Compare commits

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

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 00:30:59 -08:00
Felix Rieseberg 363febdb73 Merge pull request #279 from electron/notarize
build: Notarize the build with Apple
2019-12-04 13:43:38 -08:00
Felix Rieseberg 062ca4e205 build: Update to electron-forge b46 2019-12-04 10:44:13 -08:00
Felix Rieseberg 49053342a5 Merge pull request #278 from electron/electron-713
chore: Update Electron to 7.1.3
2019-12-04 10:09:10 -08:00
Felix Rieseberg fd582c67a8 build: Use a later macOS image 2019-12-03 18:08:51 -08:00
Felix Rieseberg 706b18808b build: Notarize the build with Apple 2019-12-03 17:52:52 -08:00
Felix Rieseberg a6ae8d5376 chore: Update Electron to 7.1.3 2019-12-03 17:45:02 -08:00
Erick Zhao 5034f8c853 feat: add bisect helper (#257) 2019-11-22 18:46:58 -05:00
Letra e916247013 fix: ensure dev tools only open in dev mode (#277) 2019-11-15 09:22:14 -05:00
Felix Rieseberg c052686de4 0.11.1 2019-11-12 14:39:38 +01:00
Erick Zhao 31aacef131 fix: update folder selection dialog for Electron 7 (#271) 2019-11-05 16:51:04 -05:00
Erick Zhao 932369b705 fix: update deprecated app.setName API (#273) 2019-11-05 16:24:43 -05:00
Shelley Vohr b835c34786 fix: update dialog example for promisification (#272) 2019-11-03 11:36:00 -05:00
Felix Rieseberg c9e1ba815a 0.11.0 2019-10-31 11:49:23 -04:00
53 changed files with 5968 additions and 3307 deletions
-1
View File
@@ -27,7 +27,6 @@ compile_commands.json
.cache
yarn-error.log
.DS_Store
.webpack
# Coverage
coverage
+1 -1
View File
@@ -4,7 +4,7 @@ os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
osx_image: xcode10
sudo: false
cache:
+37 -19
View File
@@ -7,14 +7,14 @@ const packageJson = require('./package.json')
const { version } = packageJson
const iconDir = path.resolve(__dirname, 'assets', 'icons')
module.exports = {
const config = {
hooks: {
//generateAssets: require('./tools/generateAssets')
generateAssets: require('./tools/generateAssets')
},
packagerConfig: {
name: 'Electron Fiddle',
executableName: 'electron-fiddle',
asar: false,
asar: true,
icon: path.resolve(__dirname, 'assets', 'icons', 'fiddle'),
// TODO: FIXME?
// ignore: [
@@ -32,7 +32,12 @@ module.exports = {
OriginalFilename: 'Electron Fiddle',
},
osxSign: {
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)'
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
'hardened-runtime': true,
'gatekeeper-assess': false,
'entitlements': 'static/entitlements.plist',
'entitlements-inherit': 'static/entitlements.plist',
'signature-flags': 'library'
}
},
makers: [
@@ -93,20 +98,33 @@ module.exports = {
prerelease: false
}
}
],
plugins: [
['@electron-forge/plugin-webpack', {
mainConfig: './webpack.main.config.js',
renderer: {
config: './webpack.renderer.config.js',
entryPoints: [
{
html: './static/index.html',
js: './src/renderer/app.tsx',
name: 'main_window'
}
]
}
}]
]
}
function notarizeMaybe() {
if (process.platform !== 'darwin') {
return;
}
if (!process.env.CI) {
console.log(`Not in CI, skipping notarization`);
return;
}
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
return;
}
config.packagerConfig.osxNotarize = {
appBundleId: 'com.electron.fiddle',
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
ascProvider: 'LT94ZKYDCJ'
}
}
notarizeMaybe()
// Finally, export it
module.exports = config
+4305 -2833
View File
File diff suppressed because it is too large Load Diff
+18 -27
View File
@@ -1,21 +1,24 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.10.0",
"version": "0.11.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": ".webpack/main",
"main": "./dist/src/main/main",
"scripts": {
"contributors": "node ./tools/contributors.js",
"less": "node ./tools/lessc.js",
"lint:style": "stylelint ./src/less/*.less --fix",
"lint:ts": "tslint -c tslint.json -p tsconfig.json -e \"node_modules/**/*.ts\" --fix",
"lint:tests": "tslint ./tests/**/*.ts{,x} -c tslint.json --fix",
"lint:templates": "standard ./static/show-me/**/*.js",
"lint": "npm-run-all lint:*",
"make": "npm run contributors && electron-forge make",
"package": "npm run contributors && electron-forge package",
"publish": "npm run contributors && electron-forge publish",
"start": "electron-forge start",
"make": "electron-forge make",
"package": "electron-forge package",
"parcel:build": "node ./tools/parcel-build.js",
"parcel:watch": "node ./tools/parcel-watch.js",
"publish": "electron-forge publish",
"start": "rimraf ./dist && electron-forge start",
"test": "jest --config=jest.json --coverage",
"test:ci": "jest --config=jest.json --coverage --runInBand",
"test:coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
@@ -60,14 +63,12 @@
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@electron-forge/cli": "^6.0.0-beta.45",
"@electron-forge/maker-deb": "^6.0.0-beta.45",
"@electron-forge/maker-rpm": "^6.0.0-beta.45",
"@electron-forge/maker-squirrel": "^6.0.0-beta.45",
"@electron-forge/maker-zip": "^6.0.0-beta.45",
"@electron-forge/plugin-webpack": "^6.0.0-beta.45",
"@electron-forge/publisher-github": "^6.0.0-beta.45",
"@marshallofsound/webpack-asset-relocator-loader": "^0.5.0",
"@electron-forge/cli": "^6.0.0-beta.46",
"@electron-forge/maker-deb": "^6.0.0-beta.46",
"@electron-forge/maker-rpm": "^6.0.0-beta.46",
"@electron-forge/maker-squirrel": "^6.0.0-beta.46",
"@electron-forge/maker-zip": "^6.0.0-beta.46",
"@electron-forge/publisher-github": "^6.0.0-beta.46",
"@types/builtin-modules": "^3.1.1",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
@@ -81,37 +82,27 @@
"@types/tmp": "0.1.0",
"chokidar": "^3.2.2",
"coveralls": "^3.0.7",
"css-loader": "^3.2.0",
"electron": "7.0.0",
"electron": "7.1.3",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
"file-loader": "^4.2.0",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"loader-utils": "^1.2.3",
"log-symbols": "^3.0.0",
"mini-css-extract-plugin": "^0.8.0",
"monaco-editor-webpack-plugin": "^1.7.0",
"node-abi": "^2.12.0",
"node-fetch": "^2.6.0",
"node-loader": "^0.6.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.4",
"react-test-renderer": "^16.11.0",
"rimraf": "^3.0.0",
"standard": "^14.3.1",
"style-loader": "^1.0.0",
"stylelint": "^11.1.1",
"stylelint-config-standard": "^19.0.0",
"ts-jest": "^24.1.0",
"ts-loader": "^6.2.1",
"tslint": "^5.20.0",
"tslint-microsoft-contrib": "^6.2.0",
"tslint-react": "^4.1.0",
"typescript": "^3.6.4",
"url-loader": "^2.2.0",
"webpack-bundle-analyzer": "^3.6.0"
"typescript": "^3.6.4"
}
}
+9 -3
View File
@@ -19,14 +19,18 @@ export enum IpcEvents {
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
SHOW_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
CLEAR_CONSOLE = 'CLEAR_CONSOLE'
CLEAR_CONSOLE = 'CLEAR_CONSOLE',
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
}
export const ipcMainEvents = [
IpcEvents.FS_SAVE_FIDDLE_DIALOG,
IpcEvents.FS_SAVE_FIDDLE,
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
];
export const ipcRendererEvents = [
@@ -46,7 +50,9 @@ export const ipcRendererEvents = [
IpcEvents.FS_SAVE_FIDDLE_FORGE,
IpcEvents.FS_SAVE_FIDDLE_GIST,
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.CLEAR_CONSOLE
IpcEvents.CLEAR_CONSOLE,
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
IpcEvents.BISECT_COMMANDS_TOGGLE
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
+4 -4
View File
@@ -1,7 +1,7 @@
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
@import "~@blueprintjs/core/lib/less/variables.less";
@import (inline) "../../node_modules/@blueprintjs/core/lib/css/blueprint.css";
@import (inline) "../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
@import (inline) "../../node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css";
@import (inline) "../../node_modules/@blueprintjs/core/lib/less/variables.less";
// Override some of the colors
.fiddle.bp3-dark {
@@ -1,3 +0,0 @@
button.version-chooser.bp3-button {
border-radius: 3px 0 0 3px;
}
+1 -1
View File
@@ -2,7 +2,6 @@
header {
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);
//border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background-color: @background-3;
-webkit-app-region: drag;
}
@@ -20,6 +19,7 @@ header {
select {
-webkit-app-region: no-drag;
margin-bottom: 0;
white-space: nowrap;
}
select {
-1
View File
@@ -16,5 +16,4 @@
@import "components/chrome-mac.less";
@import "components/editors.less";
@import "components/tour.less";
@import "components/commands-version-chooser.less";
@import "components/show-me.less";
+1 -1
View File
@@ -7,7 +7,7 @@ import { isDevMode } from '../utils/devmode';
* @returns {Promise<void>}
*/
export async function setupDevTools(): Promise<void> {
if (!isDevMode) return;
if (!isDevMode()) return;
const {
default: installExtension,
+18 -1
View File
@@ -1,4 +1,4 @@
import { dialog } from 'electron';
import { dialog, IpcMainEvent } from 'electron';
import { IpcEvents } from '../ipc-events';
import { ipcMainManager } from './ipc';
import { getOrCreateMainWindow } from './windows';
@@ -17,6 +17,10 @@ export function setupDialogs() {
showConfirmationDialog(args);
});
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
await showOpenDialog(event);
});
}
/**
@@ -42,3 +46,16 @@ function showConfirmationDialog(args: Electron.MessageBoxOptions) {
...args
});
}
async function showOpenDialog(event: IpcMainEvent) {
const { filePaths } = await dialog.showOpenDialog({
title: 'Open Folder',
properties: ['openDirectory']
});
if (!filePaths || filePaths.length < 1) {
return;
}
event.reply(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, [filePaths[0]]);
}
+2 -2
View File
@@ -1,5 +1,6 @@
import { app } from 'electron';
import { isDevMode } from '../utils/devmode';
import { setupAboutPanel } from '../utils/set-about-panel';
import { setupDevTools } from './devtools';
import { setupDialogs } from './dialogs';
@@ -15,6 +16,7 @@ import { getOrCreateMainWindow } from './windows';
*/
export async function onReady() {
await onFirstRunMaybe();
if (!isDevMode()) process.env.NODE_ENV = 'production';
getOrCreateMainWindow();
setupAboutPanel();
@@ -58,8 +60,6 @@ export function onWindowsAllClosed() {
* Exported for testing purposes.
*/
export function main() {
console.log(`Welcome to Fiddle ${app.getVersion()}`);
// Handle creating/removing shortcuts on Windows when
// installing/uninstalling.
if (shouldQuit()) {
+12 -7
View File
@@ -144,7 +144,7 @@ function getShowMeMenuItem(key: string, item: string | Templates): MenuItemConst
if (typeof item === 'string') {
return {
label: key,
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [ key ])
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key])
};
}
@@ -160,10 +160,10 @@ function getShowMeMenu(): MenuItemConstructorOptions {
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(SHOW_ME_TEMPLATES)
.map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
return {
label: 'Show Me',
submenu: showMeMenu
};
return {
label: 'Show Me',
submenu: showMeMenu
};
}
/**
@@ -251,11 +251,16 @@ export function setupMenu() {
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Soft Wrap',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'wordWrap' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['wordWrap']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Mini Map',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'minimap.enabled' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['minimap.enabled']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Bisect Helper',
click: () => ipcMainManager.send(IpcEvents.BISECT_COMMANDS_TOGGLE),
accelerator: 'CommandorControl+Shift+B',
});
}
+1 -2
View File
@@ -16,7 +16,6 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
height: 900,
minHeight: 600,
minWidth: 600,
show: true,
titleBarStyle: process.platform === 'darwin' ? 'hidden' : undefined,
acceptFirstMouse: true,
backgroundColor: '#1d2427',
@@ -37,7 +36,7 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
export function createMainWindow(): Electron.BrowserWindow {
console.log(`Creating main window`);
const browserWindow = new BrowserWindow(getMainWindowOptions());
browserWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
browserWindow.loadFile('./dist/static/index.html');
browserWindow.webContents.once('dom-ready', () => {
browserWindow.show();
-3
View File
@@ -21,9 +21,6 @@ import { appState } from './state';
import { getTheme } from './themes';
import { TouchBarManager } from './touch-bar-manager';
// Import style
require('../less/root.less');
/**
* The top-level class controlling the whole app. This is *not* a React component,
* but it does eventually render all components.
+8 -2
View File
@@ -1,6 +1,7 @@
import * as fs from 'fs-extra';
import * as fsType from 'fs-extra';
import * as path from 'path';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
import { getOfflineTypeDefinitionPath } from './fetch-types';
@@ -23,6 +24,7 @@ export class BinaryManager {
*/
public async remove(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
// utility to re-run removal functions upon failure
@@ -72,6 +74,7 @@ export class BinaryManager {
*/
public async setup(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
const { promisify } = await import('util');
const eDownload = promisify(require('electron-download'));
@@ -140,6 +143,7 @@ export class BinaryManager {
* @returns {Promise<Array<string>>}
*/
public async getDownloadedVersions(): Promise<Array<string>> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
console.log(`BinaryManager: Checking for downloaded versions`);
@@ -169,11 +173,13 @@ export class BinaryManager {
*/
public async getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = this.getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
public async removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
@@ -206,7 +212,7 @@ export class BinaryManager {
*/
private unzip(zipPath: string, extractPath: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const extract = require('extract-zip');
const extract = (await fancyImport<any>('extract-zip')).default;
process.noAsar = true;
+58
View File
@@ -0,0 +1,58 @@
import { ElectronVersion } from '../interfaces';
export class Bisector {
public revList: Array<ElectronVersion>;
public minRev: number;
public maxRev: number;
private pivot: number;
constructor(revList: Array<ElectronVersion>) {
this.getCurrentVersion = this.getCurrentVersion.bind(this);
this.continue = this.continue.bind(this);
this.calculatePivot = this.calculatePivot.bind(this);
this.revList = revList;
this.minRev = 0;
this.maxRev = revList.length - 1;
this.calculatePivot();
}
public getCurrentVersion() {
return this.revList[this.pivot];
}
public continue(isGoodVersion: boolean) {
let isBisectOver = false;
if (this.maxRev - this.minRev <= 1) {
isBisectOver = true;
}
if (isGoodVersion) {
const upPivot = Math.floor((this.maxRev - this.pivot) / 2) + this.pivot;
this.minRev = this.pivot;
if (upPivot !== this.maxRev && upPivot !== this.pivot) {
this.pivot = upPivot;
} else {
isBisectOver = true;
}
} else {
const downPivot = Math.floor((this.pivot - this.minRev) / 2) + this.minRev;
this.maxRev = this.pivot;
if (downPivot !== this.minRev && downPivot !== this.pivot) {
this.pivot = downPivot;
} else {
isBisectOver = true;
}
}
if (isBisectOver) {
return [this.revList[this.minRev], this.revList[this.maxRev]];
} else {
return this.revList[this.pivot];
}
}
private calculatePivot() {
this.pivot = Math.floor((this.maxRev - this.minRev) / 2);
}
}
@@ -0,0 +1,68 @@
import { Button } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
interface BisectHandlerProps {
appState: AppState;
}
@observer
export class BisectHandler extends React.Component<BisectHandlerProps> {
constructor(props: BisectHandlerProps) {
super(props);
this.continueBisect = this.continueBisect.bind(this);
this.terminateBisect = this.terminateBisect.bind(this);
}
public continueBisect(isGood: boolean) {
const { appState } = this.props;
const response = appState.Bisector!.continue(isGood);
if (Array.isArray(response)) {
this.terminateBisect();
const [minRev, maxRev] = response;
appState.pushOutput(`[BISECT] Complete: Check between versions ${minRev.version} and ${maxRev.version}.`);
} else {
appState.setVersion(response.version);
}
}
public terminateBisect() {
const { appState } = this.props;
appState.Bisector = undefined;
}
public render() {
const { appState } = this.props;
if (!!appState.Bisector) {
return (
<>
<Button
icon={'thumbs-up'}
onClick={() => this.continueBisect(true)}
/>
<Button
icon={'thumbs-down'}
onClick={() => this.continueBisect(false)}
/>
<Button
icon={'cross'}
onClick={this.terminateBisect}
>
Cancel Bisect
</Button>
</>
);
} else {
return (
<Button
icon='git-branch'
text='Bisect'
onClick={appState.toggleBisectDialog}
/>
);
}
}
}
@@ -1,4 +1,4 @@
import { Button, MenuItem } from '@blueprintjs/core';
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -71,7 +71,7 @@ export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) =
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
return null;
}
return (
@@ -95,6 +95,30 @@ export interface VersionChooserProps {
appState: AppState;
}
export const getVersionsFromAppState = (appState: AppState) => {
const { versions, versionsToShow, statesToShow } = appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
};
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
@@ -120,48 +144,29 @@ export class VersionChooser extends React.Component<VersionChooserProps, Version
this.props.appState.setVersion(version);
}
public getItems(): Array<ElectronVersion> {
const { versions, versionsToShow, statesToShow } = this.props.appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
}
public render() {
const { currentElectronVersion } = this.props.appState;
const { currentElectronVersion, Bisector } = this.props.appState;
const { version } = currentElectronVersion;
return (
<ElectronVersionSelect
filterable={true}
items={this.getItems()}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
/>
</ElectronVersionSelect>
<ButtonGroup>
<ElectronVersionSelect
filterable={true}
items={getVersionsFromAppState(this.props.appState)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!Bisector}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
disabled={!!Bisector}
/>
</ElectronVersionSelect>
</ButtonGroup>
);
}
}
+11
View File
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddressBar } from './commands-address-bar';
import { BisectHandler } from './commands-bisect';
import { EditorDropdown } from './commands-editors';
import { PublishButton } from './commands-publish-button';
import { Runner } from './commands-runner';
@@ -28,6 +29,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
public render() {
const { appState } = this.props;
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
return (
<div className='commands'>
@@ -36,6 +38,15 @@ export class Commands extends React.Component<CommandsProps, {}> {
<VersionChooser appState={appState} />
<Runner appState={appState} />
</ControlGroup>
{
// tslint:disable-next-line jsx-no-multiline-js
isBisectCommandShowing &&
(
<ControlGroup fill={true} vertical={false}>
<BisectHandler appState={appState} />
</ControlGroup>
)
}
<ControlGroup fill={true} vertical={false}>
<Button
active={appState.isConsoleShowing}
+31 -24
View File
@@ -6,7 +6,9 @@ import * as React from 'react';
import * as semver from 'semver';
import { NpmVersion } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { getElectronNameForPlatform } from '../../utils/electron-name';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
export interface AddVersionDialogProps {
@@ -16,7 +18,7 @@ export interface AddVersionDialogProps {
export interface AddVersionDialogState {
isValidElectron: boolean;
isValidVersion: boolean;
file?: File;
folderPath?: string;
version: string;
}
@@ -39,8 +41,15 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onChangeFile = this.onChangeFile.bind(this);
this.onChangeVersion = this.onChangeVersion.bind(this);
ipcRendererManager.on(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, (_event, [file]) => {
this.setFolderPath(file);
});
}
public componentWillUnmount() {
ipcRendererManager.removeAllListeners(IpcEvents.LOAD_LOCAL_VERSION_FOLDER);
}
/**
@@ -48,16 +57,11 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
const { files } = event.target as any;
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const file = files && files[0] ? files[0] : undefined;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
const isValidElectron = !!(file
&& file.path
&& await binaryManager.getIsDownloaded('custom', file.path));
this.setState({ file, isValidElectron });
this.setState({ folderPath, isValidElectron });
}
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
@@ -67,7 +71,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.setState({
version,
isValidVersion,
});
});
}
/**
@@ -76,18 +80,18 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { file, version } = this.state;
const { folderPath, version } = this.state;
if (!file) return;
if (!folderPath) return;
const name = file.path
const name = folderPath
.slice(-20)
.split(path.sep)
.slice(1)
.join(path.sep);
const toAdd: NpmVersion = {
localPath: file.path,
localPath: folderPath,
version,
name
};
@@ -129,12 +133,16 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
public render() {
const { isAddVersionDialogShowing } = this.props.appState;
const inputProps = { webkitdirectory: 'true' };
const { file } = this.state;
const inputProps = {
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
e.preventDefault();
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
}
};
const { folderPath } = this.state;
const text = file && file.path
? file.path
: `Select the folder containing ${getElectronNameForPlatform()}...`;
const text = folderPath ||
`Select the folder containing ${getElectronNameForPlatform()}...`;
return (
<Dialog
@@ -145,7 +153,6 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
>
<div className='bp3-dialog-body'>
<FileInput
onInputChange={this.onChangeFile}
id='custom-electron-version'
inputProps={inputProps as any}
text={text}
@@ -163,9 +170,9 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
}
private renderPath(): JSX.Element | null {
const { file, isValidElectron } = this.state;
const { folderPath, isValidElectron } = this.state;
if (!file || !file.path) return null;
if (!folderPath) return null;
const info = isValidElectron
? `We found an ${getElectronNameForPlatform()} in this folder.`
@@ -208,7 +215,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
isValidElectron: false,
isValidVersion: false,
version: '',
file: undefined
folderPath: undefined
});
}
}
+164
View File
@@ -0,0 +1,164 @@
import { Button, Dialog, Label, MenuItem } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { Bisector } from '../bisect';
import { AppState } from '../state';
import { filterItem, getItemIcon, getVersionsFromAppState, renderItem } from './commands-version-chooser';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
export interface BisectDialogProps {
appState: AppState;
}
export interface BisectDialogState {
startIndex?: number;
endIndex?: number;
allVersions: Array<ElectronVersion>;
}
/**
* The "add version" dialog allows users to add custom builds of Electron.
*
* @class AddVersionDialog
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
*/
@observer
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
constructor(props: BisectDialogProps) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onBeginSelect = this.onBeginSelect.bind(this);
this.onEndSelect = this.onEndSelect.bind(this);
const allVersions = getVersionsFromAppState(this.props.appState);
this.state = { allVersions };
}
public onBeginSelect(version: ElectronVersion) {
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
}
public onEndSelect(version: ElectronVersion) {
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
}
/**
* Handles the submission of the dialog
*
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { endIndex, startIndex, allVersions } = this.state;
const { appState } = this.props;
if (endIndex === undefined || startIndex === undefined) {
return;
}
const bisectRange = allVersions
.slice(endIndex, startIndex + 1)
.reverse();
appState.Bisector = new Bisector(bisectRange);
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
appState.setVersion(initialBisectPivot);
this.onClose();
}
/**
* Closes the dialog
*/
public onClose() {
this.props.appState.isBisectDialogShowing = false;
}
get buttons() {
const canSubmit =
!!this.state.startIndex &&
!!this.state.endIndex &&
this.state.startIndex > this.state.endIndex;
return [
(
<Button
icon='play'
key='submit'
disabled={!canSubmit}
onClick={this.onSubmit}
text='Begin'
/>
), (
<Button
icon='cross'
key='cancel'
onClick={this.onClose}
text='Cancel'
/>
)
];
}
public render() {
const { isBisectDialogShowing } = this.props.appState;
const { startIndex, endIndex, allVersions } = this.state;
return (
<Dialog
isOpen={isBisectDialogShowing}
onClose={this.onClose}
title='Start a bisect session'
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
<Label>
Earliest Version
<ElectronVersionSelect
filterable={true}
items={allVersions}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onBeginSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
text={startIndex ? `v${allVersions[startIndex].version}` : ``}
icon={startIndex ? getItemIcon(allVersions[startIndex]) : 'small-minus'}
fill={true}
/>
</ElectronVersionSelect>
</Label>
<Label>
Latest Version
<ElectronVersionSelect
filterable={true}
items={allVersions.slice(0, startIndex!)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onEndSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!startIndex}
>
<Button
text={endIndex ? `v${allVersions[endIndex].version}` : ``}
icon={endIndex ? getItemIcon(allVersions[endIndex]) : 'small-minus'}
fill={true}
disabled={!startIndex}
/>
</ElectronVersionSelect>
</Label>
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
}
+13 -2
View File
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddThemeDialog } from './dialog-add-theme';
import { AddVersionDialog } from './dialog-add-version';
import { BisectDialog } from './dialog-bisect';
import { ConfirmDialog } from './dialog-confirm';
import { TokenDialog } from './dialog-token';
import { WarningDialog } from './dialog-warning';
@@ -23,7 +24,13 @@ export interface DialogsProps {
export class Dialogs extends React.Component<DialogsProps, {}> {
public render() {
const { appState } = this.props;
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing, isThemeDialogShowing } = appState;
const {
isTokenDialogShowing,
isSettingsShowing,
isAddVersionDialogShowing,
isThemeDialogShowing,
isBisectDialogShowing
} = appState;
const maybeToken = isTokenDialogShowing
? <TokenDialog key='dialogs' appState={appState} />
: null;
@@ -35,9 +42,12 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
: null;
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
: null;
const maybeBisect = isBisectDialogShowing
? <BisectDialog key='bisect-dialog' appState={appState} />
: null;
const eitherWarningOrPrompt = appState.isWarningDialogShowing
? <WarningDialog appState={appState} />
: <ConfirmDialog appState={appState}/>;
: <ConfirmDialog appState={appState} />;
return (
<div key='dialogs' className='dialogs'>
@@ -45,6 +55,7 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
{maybeSettings}
{maybeAddLocalVersion}
{maybeMonaco}
{maybeBisect}
{eitherWarningOrPrompt}
</div>
);
+9 -6
View File
@@ -1,7 +1,6 @@
import { reaction } from 'mobx';
import { observer } from 'mobx-react';
// tslint:disable-next-line:no-submodule-imports
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import * as MonacoType from 'monaco-editor';
import * as React from 'react';
import { Mosaic, MosaicBranch, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
@@ -21,7 +20,7 @@ import { renderNonIdealState } from './editors-non-ideal-state';
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
import { ShowMe } from './show-me';
const defaultMonacoOptions: monaco.editor.IEditorOptions = {
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
minimap: {
enabled: false
},
@@ -41,9 +40,9 @@ export interface EditorsProps {
}
export interface EditorsState {
monaco?: typeof monaco;
monaco?: typeof MonacoType;
isMounted?: boolean;
monacoOptions: monaco.editor.IEditorOptions;
monacoOptions: MonacoType.editor.IEditorOptions;
}
@observer
@@ -133,7 +132,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
Object.keys(window.ElectronFiddle.editors)
.forEach((key) => {
const editor: monaco.editor.IStandaloneCodeEditor | null
const editor: MonacoType.editor.IStandaloneCodeEditor | null
= window.ElectronFiddle.editors[key];
if (editor) {
@@ -230,6 +229,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
*/
public renderEditor(id: EditorId): JSX.Element | null {
const { appState } = this.props;
const { monaco } = this.state;
return (
<Editor
@@ -243,6 +243,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
public render() {
const { appState } = this.props;
const { monaco } = this.state;
if (!monaco) return null;
@@ -272,6 +273,8 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
*/
public async loadMonaco(): Promise<void> {
const { app } = window.ElectronFiddle;
const loader = require('monaco-loader');
const monaco = app.monaco || await loader();
if (!app.monaco) {
app.monaco = monaco;
@@ -1,7 +1,7 @@
import { Button, Callout, FormGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { shell } from 'electron';
import * as fs from 'fs-extra';
import * as fsType from 'fs-extra';
import { observer } from 'mobx-react';
import * as path from 'path';
import * as React from 'react';
@@ -113,6 +113,7 @@ export class AppearanceSettings extends React.Component<
*/
public async createNewThemeFromCurrent(): Promise<boolean> {
const { appState } = this.props;
const fs = await fancyImport<typeof fsType>('fs-extra');
const theme = await getTheme(appState.theme);
try {
@@ -146,6 +147,8 @@ export class AppearanceSettings extends React.Component<
* @returns {Promise<boolean>}
*/
public async openThemeFolder(): Promise<boolean> {
const fs = await fancyImport<typeof fsType>('fs-extra');
try {
await fs.ensureDir(THEMES_PATH);
await shell.showItemInFolder(THEMES_PATH);
+5 -1
View File
@@ -1,9 +1,10 @@
import * as fs from 'fs-extra';
import * as fsType from 'fs-extra';
import * as MonacoType from 'monaco-editor';
import * as path from 'path';
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
import { callIn } from '../utils/call-in';
import { fancyImport } from '../utils/import';
import { USER_DATA_PATH } from './constants';
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
@@ -52,6 +53,7 @@ export function getOfflineTypeDefinitionPath(version: string): string {
* @returns {boolean}
*/
export async function getOfflineTypeDefinitions(version: string): Promise<boolean> {
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(getOfflineTypeDefinitionPath(version));
}
@@ -63,6 +65,7 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
* @returns {void}
*/
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
const fs = await fancyImport<typeof fsType>('fs-extra');
await fs.mkdirp(definitionPath);
const offlinePath = getOfflineTypeDefinitionPath(version.version);
@@ -90,6 +93,7 @@ export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Pr
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
if (version.source === ElectronVersionSource.local && !!version.localPath) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typesPath = getLocalTypePathForVersion(version);
if (!!typesPath && fs.existsSync(typesPath)) {
return fs.readFile(typesPath, 'utf-8');
+8 -1
View File
@@ -1,10 +1,11 @@
import * as fs from 'fs-extra';
import * as fsType from 'fs-extra';
import * as path from 'path';
import { EditorValues, Files, FileTransform } from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
import { fancyImport } from '../utils/import';
import { ipcRendererManager } from './ipc';
import { AppState } from './state';
import { getTemplateValues } from './templates';
@@ -148,6 +149,8 @@ export class FileManager {
*/
public async cleanup(dir?: string): Promise<boolean> {
if (dir) {
const fs = await fancyImport<typeof fsType>('fs-extra');
if (fs.existsSync(dir)) {
try {
await fs.remove(dir);
@@ -172,6 +175,7 @@ export class FileManager {
public async saveToTemp(
options: PackageJsonOptions, ...transforms: Array<FileTransform>
): Promise<string> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const tmp = await import('tmp');
const files = await this.getFiles(options, ...transforms);
const dir = tmp.dirSync();
@@ -199,6 +203,7 @@ export class FileManager {
*/
private async readFile(filePath: string): Promise<string> {
try {
const fs = await fancyImport<typeof fsType>('fs-extra');
return await fs.readFile(filePath, 'utf-8');
} catch (error) {
console.log(`FileManager: Could not read ${filePath}`, error);
@@ -216,6 +221,7 @@ export class FileManager {
*/
private async saveFile(filePath: string, content: string): Promise<void> {
try {
const fs = await fancyImport<typeof fsType>('fs-extra');
return await fs.outputFile(filePath, content, { encoding: 'utf-8' });
} catch (error) {
console.log(`FileManager: Could not save ${filePath}`, error);
@@ -233,6 +239,7 @@ export class FileManager {
*/
private async removeFile(filePath: string): Promise<void> {
try {
const fs = await fancyImport<typeof fsType>('fs-extra');
return await fs.remove(filePath);
} catch (error) {
console.log(`FileManager: Could not remove ${filePath}`, error);
+55 -35
View File
@@ -1,4 +1,4 @@
import * as fs from 'fs-extra';
import * as fsType from 'fs-extra';
import { action, autorun, computed, observable, when } from 'mobx';
import { MosaicNode } from 'react-mosaic-component';
@@ -20,9 +20,11 @@ import { arrayToStringMap } from '../utils/array-to-stringmap';
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
import { getName } from '../utils/get-title';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
import { BinaryManager } from './binary';
import { Bisector } from './bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
@@ -65,7 +67,6 @@ export class AppState {
// -- Persisted settings ------------------
@observable public version: string = defaultVersion;
@observable public theme: string | null = localStorage.getItem('theme');
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
@observable public gitHubAvatarUrl: string | null = localStorage.getItem('gitHubAvatarUrl');
@observable public gitHubName: string | null = localStorage.getItem('gitHubName');
@observable public gitHubLogin: string | null = localStorage.getItem('gitHubLogin');
@@ -73,39 +74,43 @@ export class AppState {
@observable public gitHubPublishAsPublic: boolean = !!this.retrieve('gitHubPublishAsPublic');
@observable public versionsToShow: Array<ElectronReleaseChannel> =
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown];
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
@observable public binaryManager: BinaryManager = new BinaryManager();
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
// -- Various session-only state ------------------
@observable public gistId: string = '';
@observable public isPublishing: boolean = false;
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | undefined;
@observable public isUpdatingElectronVersions = false;
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public confirmationDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public warningDialogLastResult: boolean | null = null;
@observable public confirmationPromptLastResult: boolean | null = null;
@observable public isRunning = false;
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
@observable public templateName: string | undefined;
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
@observable public localTypeWatcher: fsType.FSWatcher | undefined;
@observable public binaryManager: BinaryManager = new BinaryManager();
@observable public Bisector: Bisector | undefined;
@observable public isPublishing: boolean = false;
@observable public isRunning: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isUpdatingElectronVersions: boolean = false;
// -- Various "isShowing" settings ------------------
@observable public isBisectCommandShowing: boolean;
@observable public isConsoleShowing: boolean = false;
@observable public isTokenDialogShowing: boolean = false;
@observable public isWarningDialogShowing: boolean = false;
@observable public isConfirmationPromptShowing: boolean = false;
@observable public isSettingsShowing: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isBisectDialogShowing: boolean = false;
@observable public isAddVersionDialogShowing: boolean = false;
@observable public isThemeDialogShowing: boolean = false;
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
@@ -127,15 +132,18 @@ export class AppState {
this.setVersion = this.setVersion.bind(this);
this.showTour = this.showTour.bind(this);
this.signOutGitHub = this.signOutGitHub.bind(this);
this.toggleBisectCommands = this.toggleBisectCommands.bind(this);
this.toggleAuthDialog = this.toggleAuthDialog.bind(this);
this.toggleConsole = this.toggleConsole.bind(this);
this.clearConsole = this.clearConsole.bind(this);
this.toggleSettings = this.toggleSettings.bind(this);
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
this.updateElectronVersions = this.updateElectronVersions.bind(this);
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
ipcRendererManager.on(IpcEvents.CLEAR_CONSOLE, this.clearConsole);
ipcRendererManager.on(IpcEvents.BISECT_COMMANDS_TOGGLE, this.toggleBisectCommands);
// Setup auto-runs
autorun(() => this.save('theme', this.theme));
@@ -241,6 +249,13 @@ export class AppState {
this.output = [];
}
@action public toggleBisectCommands() {
// guard against hiding the commands when executing a bisect
if (!this.Bisector && !this.isBisectDialogShowing) {
this.isBisectCommandShowing = !this.isBisectCommandShowing;
}
}
@action public toggleAddVersionDialog() {
this.isAddVersionDialogShowing = !this.isAddVersionDialogShowing;
}
@@ -261,6 +276,10 @@ export class AppState {
}
}
@action public toggleBisectDialog() {
this.isBisectDialogShowing = !this.isBisectDialogShowing;
}
@action public toggleConfirmationPromptDialog() {
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
@@ -317,12 +336,12 @@ export class AppState {
this.updateDownloadedVersionState();
}
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async removeVersion(input: string) {
const version = normalizeVersion(input);
const release = this.versions[version];
@@ -356,12 +375,12 @@ export class AppState {
this.updateDownloadedVersionState();
}
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async downloadVersion(input: string) {
const version = normalizeVersion(input);
console.log(`State: Downloading Electron ${version}`);
@@ -385,12 +404,12 @@ export class AppState {
}
}
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async setVersion(input: string) {
const version = normalizeVersion(input);
@@ -415,6 +434,7 @@ export class AppState {
const versionObject = this.versions[version];
if (versionObject.source === ElectronVersionSource.local) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typePath = getLocalTypePathForVersion(versionObject);
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
this.localTypeWatcher = fs.watch(typePath!, async () => {
@@ -434,11 +454,11 @@ export class AppState {
await this.downloadVersion(version);
}
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
@action public async updateDownloadedVersionState(): Promise<void> {
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
const updatedVersions = { ...this.versions };
@@ -575,7 +595,7 @@ export class AppState {
*/
@action public showMosaic(id: MosaicId) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
this.setVisibleMosaics([ ...currentlyVisible, id ]);
this.setVisibleMosaics([...currentlyVisible, id]);
}
/**
+5 -2
View File
@@ -1,8 +1,9 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as fsExtraType from 'fs-extra';
import * as pathType from 'path';
import { EditorValues } from '../interfaces';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { fancyImport } from '../utils/import';
/**
* Returns expected content for a given name.
@@ -11,6 +12,8 @@ import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from
* @returns {Promise<EditorValues>}
*/
export async function getTemplateValues(name: string): Promise<EditorValues> {
const path = await fancyImport<typeof pathType>('path');
const fs = await fancyImport<typeof fsExtraType>('fs-extra');
const templatesPath = path.join(__dirname, '../../static/show-me');
const templatePath = path.join(templatesPath, name.toLowerCase());
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
+2
View File
@@ -6,9 +6,11 @@
<title>Electron Fiddle</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../src/less/root.less">
<style id="fiddle-theme"></style>
</head>
<body class="fiddle">
<div id="app"></div>
<script src="../src/renderer/app.tsx"></script>
</body>
</html>
+10 -7
View File
@@ -12,13 +12,16 @@ app.on('ready', () => {
// Show an "Open File" dialog and attempt to open
// the chosen file in our window.
//
// If you provide a callback, the method will be asynchronous,
// but in thise case, we'll just use the synchronous variant.
const file = dialog.showOpenDialog({
title: 'Hello!',
dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
}).then(result => {
if (result.canceled) {
console.log('Dialog was canceled')
} else {
const file = result.filePaths[0]
mainWindow.loadURL(`file://${file}`)
}
}).catch(err => {
console.log(err)
})
mainWindow.loadURL(`file://${file}`)
})
+19 -12
View File
@@ -1,12 +1,6 @@
import { setupDevTools } from '../../src/main/devtools';
let mockIsDevMode = false;
jest.mock('../../src/utils/devmode', () => ({
get isDevMode() {
return mockIsDevMode;
}
}));
import { isDevMode } from '../../src/utils/devmode';
jest.mock('../../src/utils/devmode');
jest.mock('electron-devtools-installer', () => ({
default: jest.fn(),
@@ -17,18 +11,31 @@ jest.mock('electron-devtools-installer', () => ({
describe('devtools', () => {
it('does not set up developer tools if not in dev mode', () => {
const devtools = require('electron-devtools-installer');
(isDevMode as jest.Mock).mockReturnValue(false);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(0);
});
it('sets up developer tools if not in dev mode', () => {
it('sets up developer tools if in dev mode', () => {
const devtools = require('electron-devtools-installer');
mockIsDevMode = true;
(isDevMode as jest.Mock).mockReturnValue(true);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(1);
});
it('catch error in setting up developer tools', async (done) => {
const devtools = require('electron-devtools-installer');
// throw devtool error
devtools.default.mockRejectedValue(new Error('devtool error'));
(isDevMode as jest.Mock).mockReturnValue(true);
try {
await setupDevTools();
done();
} catch (e) {
expect(e).toMatch('error');
}
});
});
+79 -9
View File
@@ -3,26 +3,96 @@ import { setupDialogs } from '../../src/main/dialogs';
import { ipcMainManager } from '../../src/main/ipc';
import { dialog } from 'electron';
import { flushPromises } from '../utils';
jest.mock('../../src/main/windows');
describe('dialogs', () => {
it('sets up dialogs', () => {
setupDialogs();
beforeEach(() => {
setupDialogs();
});
it('sets up dialogs', () => {
expect(ipcMainManager.eventNames()).toEqual([
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
]);
});
it('tries to show a dialog if triggered', () => {
setupDialogs();
describe('warning dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
describe('confirmation dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_CONFIRMATION_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
describe('local version folder dialog', () => {
it('shows dialog when triggering IPC event', () => {
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: jest.fn()
});
expect(dialog.showOpenDialog).toHaveBeenCalledWith(expect.objectContaining({
properties: ['openDirectory']
}));
});
it('triggers IPC load local version event', async () => {
const replyFn = jest.fn();
const paths = ['/test/path/'];
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: paths
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).toHaveBeenCalledWith(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, paths);
});
it('does nothing if not given a path', async () => {
const replyFn = jest.fn();
// empty array
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
// nothing in response
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
});
});
});
+16 -2
View File
@@ -78,6 +78,20 @@ describe('menu', () => {
(toggleMap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledTimes(2);
});
it('adds Bisect toggle', () => {
overridePlatform('linux');
setupMenu();
const result = (electron.Menu.buildFromTemplate as any).mock.calls[0][0];
const submenu = result[2].submenu as Array<Electron.MenuItemConstructorOptions>;
const toggleSoftWrap = submenu.find(({ label }) => label === 'Toggle Bisect Helper');
(toggleSoftWrap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledWith(IpcEvents.BISECT_COMMANDS_TOGGLE);
});
});
describe('menu groups', () => {
@@ -174,7 +188,7 @@ describe('menu', () => {
it('attempts to open a template on click', () => {
showMe.submenu[0].submenu[0].click();
expect(ipcMainManager.send)
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, [ 'App' ]);
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, ['App']);
});
});
@@ -182,7 +196,7 @@ describe('menu', () => {
let tasks: any;
beforeEach(() => {
const mock = (electron.Menu.buildFromTemplate as any).mock;
const mock = (electron.Menu.buildFromTemplate as any).mock;
const menu = mock.calls[0][0];
tasks = menu[menu.length - 3];
});
+70
View File
@@ -0,0 +1,70 @@
import { ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
}));
describe('bisect', () => {
let bisector: Bisector;
beforeEach(() => {
const versions = generateVersionRange(9);
bisector = new Bisector(versions);
});
it('selects a pivot in the middle of the array', () => {
const pivot = bisector.getCurrentVersion();
const middleIndex = Math.floor(bisector.revList.length / 2);
expect(pivot).toBe(bisector.revList[middleIndex]);
});
describe('continue()', () => {
it('returns the current version', () => {
const result = bisector.continue(true);
const version = bisector.getCurrentVersion();
expect(result).toBe(version);
});
it('discards lower half of the range if pivot is good version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(true);
expect(bisector.revList[bisector.minRev]).toBe(pivotVersion);
});
it('discards upper half of the range if pivot is bad version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(false);
expect(bisector.revList[bisector.maxRev]).toBe(pivotVersion);
});
it('terminates if fewer than 2 items are left', () => {
const versions = generateVersionRange(2);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseGood = bisector.continue(true);
expect(responseGood).toHaveLength(2);
expect(versions).toContain(responseGood[0]);
expect(versions).toContain(responseGood[1]);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseBad = bisector.continue(false);
expect(responseBad).toHaveLength(2);
expect(versions).toContain(responseBad[0]);
expect(versions).toContain(responseBad[1]);
});
});
describe('getCurrentVersion()', () => {
it('returns a version within the range', () => {
const version = bisector.getCurrentVersion();
expect(bisector.revList).toContain(version);
});
});
});
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bisect commands component renders bisect dialog button if no bisect instance 1`] = `
<Blueprint3.Button
icon="git-branch"
text="Bisect"
/>
`;
exports[`Bisect commands component renders helper buttons if bisect instance is active 1`] = `
<Fragment>
<Blueprint3.Button
icon="thumbs-up"
onClick={[Function]}
/>
<Blueprint3.Button
icon="thumbs-down"
onClick={[Function]}
/>
<Blueprint3.Button
icon="cross"
onClick={[Function]}
>
Cancel Bisect
</Blueprint3.Button>
</Fragment>
`;
@@ -1,46 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VersionChooser component handles corrupt data 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
exports[`VersionChooser component renderItem() renders an item 1`] = `
@@ -62,44 +66,48 @@ exports[`VersionChooser component renderItem() renders an item 1`] = `
`;
exports[`VersionChooser component renders 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
@@ -16,10 +16,9 @@ exports[`AddVersionDialog component renders 1`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -79,10 +78,9 @@ exports[`AddVersionDialog component renders 2`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -0,0 +1,413 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BisectDialog component renders 1`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 2`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={true}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={true}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 3`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
@@ -0,0 +1,86 @@
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { BisectHandler } from '../../../src/renderer/components/commands-bisect';
describe('Bisect commands component', () => {
let store: any;
beforeEach(() => {
store = {
Bisector: {
continue: jest.fn(),
getCurrentVersion: jest.fn()
},
setVersion: jest.fn(),
version: '1.0.0',
pushOutput: jest.fn()
};
});
it('renders helper buttons if bisect instance is active', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
expect(wrapper).toMatchSnapshot();
});
it('renders bisect dialog button if no bisect instance', () => {
delete store.Bisector;
const wrapper = shallow(<BisectHandler appState={store} />);
expect(wrapper).toMatchSnapshot();
});
describe('buttons', () => {
let wrapper: ShallowWrapper;
let instance: BisectHandler;
beforeEach(() => {
wrapper = shallow(<BisectHandler appState={store} />);
instance = wrapper.instance() as any;
instance.continueBisect = jest.fn();
});
it('passes in Version=Good when thumbs up button is pressed', () => {
wrapper.find('[icon="thumbs-up"]').simulate('click');
expect(instance.continueBisect).toHaveBeenCalledWith(true);
});
it('passes in Version=Bad when thumbs down button is pressed', () => {
wrapper.find('[icon="thumbs-down"]').simulate('click');
expect(instance.continueBisect).toHaveBeenCalledWith(false);
});
});
describe('continueBisect()', () => {
it('sets version assigned by bisect algorithm', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
store.Bisector.continue.mockReturnValue({
version: '2.0.0'
});
instance.continueBisect(true);
expect(store.setVersion).toHaveBeenCalledWith('2.0.0');
});
it('terminates bisect if algorithm returns array', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
instance.terminateBisect = jest.fn();
// same value is only returned when there is only 1 version left
store.Bisector.continue.mockReturnValue(['minRev', 'maxRev']);
instance.continueBisect(true);
expect(store.setVersion).not.toHaveBeenCalled();
expect(instance.terminateBisect).toHaveBeenCalled();
expect(store.pushOutput).toHaveBeenCalled();
});
});
describe('terminateBisect()', () => {
it('removes the bisect instance from the app state', () => {
const wrapper = shallow(<BisectHandler appState={store} />);
const instance: BisectHandler = wrapper.instance() as any;
instance.terminateBisect();
expect(store.Bisector).toBeUndefined();
});
});
});
@@ -1,15 +1,16 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { IpcEvents } from '../../../src/ipc-events';
import { AddVersionDialog } from '../../../src/renderer/components/dialog-add-version';
import { ipcRendererManager } from '../../../src/renderer/ipc';
import { overridePlatform, resetPlatform } from '../../utils';
jest.mock('../../../src/renderer/ipc');
describe('AddVersionDialog component', () => {
let store: any;
const mockFile = {
path: '/test/file'
};
const mockFile = '/test/file';
beforeAll(() => {
// We render the buttons different depending on the
@@ -37,7 +38,7 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
isValidVersion: true,
isValidElectron: true,
file: mockFile
folderPath: mockFile
});
expect(wrapper).toMatchSnapshot();
@@ -45,35 +46,32 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
isValidVersion: false,
isValidElectron: true,
file: mockFile
folderPath: mockFile
});
expect(wrapper).toMatchSnapshot();
});
describe('onChangeFile()', () => {
it('handles the change event', async () => {
it('overrides default input with Electron dialog', () => {
const preventDefault = jest.fn();
const wrapper = shallow(<AddVersionDialog appState={store} />);
const inp = wrapper.find('#custom-electron-version');
inp.dive().find('input[type="file"]').simulate('click', { preventDefault });
expect(ipcRendererManager.send as jest.Mock)
.toHaveBeenCalledWith(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
expect(preventDefault).toHaveBeenCalled();
});
describe('setFolderPath()', () => {
it('does something', async () => {
store.binaryManager.getIsDownloaded.mockResolvedValue(true);
const wrapper = shallow(<AddVersionDialog appState={store} />);
await (wrapper.instance() as any).setFolderPath('/test/');
await (wrapper.instance() as any).onChangeFile({ target: { files: [ mockFile ] } });
expect(wrapper.state('file')).toBe(mockFile);
});
it('handles invalid input', async () => {
const wrapper = shallow(<AddVersionDialog appState={store} />);
await (wrapper.instance() as any).onChangeFile({ target: { files: [] } });
expect(wrapper.state('file')).toBe(undefined);
});
it('handles the change event and checks for Electron', async () => {
const wrapper = shallow(<AddVersionDialog appState={store} />);
store.binaryManager.getIsDownloaded.mockReturnValueOnce(false);
await (wrapper.instance() as any).onChangeFile({ target: { files: [ mockFile ] } });
expect(wrapper.state('file')).toBe(mockFile);
expect(wrapper.state('isValidElectron')).toBe(false);
expect(wrapper.state('isValidElectron')).toBe(true);
expect(wrapper.state('folderPath')).toBe('/test/');
});
});
@@ -93,7 +91,7 @@ describe('AddVersionDialog component', () => {
expect(wrapper.state('isValidVersion')).toBe(false);
expect(wrapper.state('version')).toBe('foo');
(wrapper.instance() as any).onChangeVersion({ target: { } });
(wrapper.instance() as any).onChangeVersion({ target: {} });
expect(wrapper.state('isValidVersion')).toBe(false);
expect(wrapper.state('version')).toBe('');
});
@@ -113,9 +111,7 @@ describe('AddVersionDialog component', () => {
wrapper.setState({
version: '3.3.3',
file: {
path: '/test/path'
}
folderPath: '/test/path'
});
await (wrapper.instance() as any).onSubmit();
@@ -0,0 +1,124 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ElectronVersionSource, ElectronVersionState } from '../../../src/interfaces';
import { Bisector } from '../../../src/renderer/bisect';
import { BisectDialog } from '../../../src/renderer/components/dialog-bisect';
import { ElectronReleaseChannel } from '../../../src/renderer/versions';
jest.mock('../../../src/renderer/bisect');
describe('BisectDialog component', () => {
let store: any;
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
}));
beforeEach(() => {
store = {
versions: generateVersionRange(5),
versionsToShow: [ElectronReleaseChannel.stable],
statesToShow: [ElectronVersionState.ready],
setVersion: jest.fn()
};
});
it('renders', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
// start and end selected
wrapper.setState({
startIndex: 3,
endIndex: 0,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
// no selection
wrapper.setState({
startIndex: undefined,
endIndex: undefined,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
// only start selected
wrapper.setState({
startIndex: 3,
endIndex: undefined,
allVersions: generateVersionRange(5)
});
expect(wrapper).toMatchSnapshot();
});
describe('onBeginSelect()', () => {
it('sets the begin version', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
const instance: BisectDialog = wrapper.instance() as any;
expect(instance.state.startIndex).toBeUndefined();
instance.onBeginSelect(store.versions[2]);
expect(instance.state.startIndex).toBe(2);
});
});
describe('onEndSelect()', () => {
it('sets the end version', () => {
const wrapper = shallow(<BisectDialog appState={store} />);
const instance: BisectDialog = wrapper.instance() as any;
expect(instance.state.endIndex).toBeUndefined();
instance.onEndSelect(store.versions[2]);
expect(instance.state.endIndex).toBe(2);
});
});
describe('onSubmit()', () => {
it('initiates a bisect instance and sets a version', async () => {
const version = '1.0.0';
(Bisector as jest.Mock).mockImplementation(() => {
return {
getCurrentVersion: () => ({ version })
};
});
const versions = generateVersionRange(5);
const wrapper = shallow(<BisectDialog appState={store} />);
wrapper.setState({
startIndex: 4,
endIndex: 0,
allVersions: versions
});
const instance: BisectDialog = wrapper.instance() as any;
await instance.onSubmit();
expect(Bisector).toHaveBeenCalledWith(versions.slice(0, 5).reverse());
expect(store.Bisector).toBeDefined();
expect(store.setVersion).toHaveBeenCalledWith(version);
});
it('does nothing if endIndex or startIndex are falsy', async () => {
const wrapper = shallow(<BisectDialog appState={store} />);
wrapper.setState({
startIndex: undefined,
endIndex: 0
});
const instance1: BisectDialog = wrapper.instance() as any;
await instance1.onSubmit();
expect(Bisector).not.toHaveBeenCalled();
wrapper.setState({
startIndex: 4,
endIndex: undefined
});
const instance2: BisectDialog = wrapper.instance() as any;
await instance2.onSubmit();
expect(Bisector).not.toHaveBeenCalled();
});
});
});
+32 -4
View File
@@ -1,4 +1,5 @@
import { ALL_MOSAICS, EditorId, ElectronVersionSource, ElectronVersionState, PanelId } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from '../../src/renderer/constants';
import { getContent, isContentUnchanged } from '../../src/renderer/content';
import { ipcRendererManager } from '../../src/renderer/ipc';
@@ -142,7 +143,7 @@ describe('AppState', () => {
expect(appState.currentElectronVersion).toEqual(mockVersions['2.0.2']);
});
});
});
describe('toggleConsole()', () => {
it('toggles the console', () => {
@@ -179,6 +180,33 @@ describe('AppState', () => {
});
});
describe('toggleBisectCommands()', () => {
it('toggles visibility of the bisect commands', () => {
const isVisible = appState.isBisectCommandShowing;
appState.toggleBisectCommands();
expect(isVisible).not.toBe(appState.isBisectCommandShowing);
});
it('takes no action if bisect dialog is active', () => {
const isVisible = appState.isBisectCommandShowing;
expect(appState.isBisectDialogShowing).toBe(false);
appState.toggleBisectDialog();
appState.toggleBisectCommands();
expect(isVisible).toBe(appState.isBisectCommandShowing);
});
it('takes no action if bisect instance is active', () => {
const isVisible = appState.isBisectCommandShowing;
appState.Bisector = new Bisector([]);
appState.toggleBisectCommands();
expect(isVisible).toBe(appState.isBisectCommandShowing);
});
});
describe('toggleAddVersionDialog()', () => {
it('toggles the add version dialog', () => {
appState.toggleAddVersionDialog();
@@ -296,7 +324,7 @@ describe('AppState', () => {
await appState.setVersion('v1.0.0');
expect(getContent).toHaveBeenCalledTimes(1);
expect(window.ElectronFiddle.app.setEditorValues).toHaveBeenCalledTimes(1);
expect(window.ElectronFiddle.app.setEditorValues).toHaveBeenCalledTimes(1);
});
});
@@ -341,7 +369,7 @@ describe('AppState', () => {
// refreshed - we didn't actually add the local version
// above, since versions.ts is mocked
expect(Object.keys(appState.versions)).toEqual(
[ '2.0.2', '2.0.1', '1.8.7' ]
['2.0.2', '2.0.1', '1.8.7']
);
});
});
@@ -426,7 +454,7 @@ describe('AppState', () => {
it('updates the visible editors and creates a backup', () => {
appState.mosaicArrangement = createMosaicArrangement(ALL_MOSAICS);
appState.closedPanels = {};
appState.setVisibleMosaics([ EditorId.main ]);
appState.setVisibleMosaics([EditorId.main]);
expect(appState.mosaicArrangement).toEqual(EditorId.main);
expect(appState.closedPanels[EditorId.renderer]).toBeTruthy();
+4
View File
@@ -13,3 +13,7 @@ export function resetPlatform() {
writable: true
});
}
export function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { isDevMode } from '../../src/utils/devmode';
describe('devMode', () => {
const old = process.defaultApp;
const old = (process as any).defaultApp; // for tsconfig error
afterEach(() => {
Object.defineProperty(process, 'defaultApp', { value: old });
+8
View File
@@ -0,0 +1,8 @@
/* tslint:disable */
const { maybeFetchContributors } = require('./contributors')
const { compileParcel } = require('./parcel-build')
module.exports = async () => {
await Promise.all([maybeFetchContributors(), compileParcel()])
}
+47
View File
@@ -0,0 +1,47 @@
/* tslint:disable */
const Bundler = require('parcel-bundler')
const path = require('path')
async function compileParcel (options = {}) {
const entryFiles = [
path.join(__dirname, '../static/index.html'),
path.join(__dirname, '../src/main/main.ts')
]
const bundlerOptions = {
outDir: './dist', // The out directory to put the build files in, defaults to dist
outFile: undefined, // The name of the outputFile
publicUrl: '../', // The url to server on, defaults to dist
watch: false, // whether to watch the files and rebuild them on change, defaults to process.env.NODE_ENV !== 'production'
cache: false, // Enabled or disables caching, defaults to true
cacheDir: '.cache', // The directory cache gets put in, defaults to .cache
contentHash: false, // Disable content hash from being included on the filename
minify: false, // Minify files, enabled if process.env.NODE_ENV === 'production'
scopeHoist: false, // turn on experimental scope hoisting/tree shaking flag, for smaller production bundles
target: 'electron', // browser/node/electron, defaults to browser
// https: { // Define a custom {key, cert} pair, use true to generate one or false to use http
// cert: './ssl/c.crt', // path to custom certificate
// key: './ssl/k.key' // path to custom key
// },
logLevel: 3, // 3 = log everything, 2 = log warnings & errors, 1 = log errors
hmr: true, // Enable or disable HMR while watching
hmrPort: 0, // The port the HMR socket runs on, defaults to a random free port (0 in node.js resolves to a random free port)
sourceMaps: true, // Enable or disable sourcemaps, defaults to enabled (minified builds currently always create sourcemaps)
hmrHostname: '', // A hostname for hot module reload, default to ''
detailedReport: false, // Prints a detailed report of the bundles, assets, filesizes and times, defaults to false, reports are only printed if watch is disabled,
...options
}
const bundler = new Bundler(entryFiles, bundlerOptions)
// Run the bundler, this returns the main bundle
// Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild
await bundler.bundle()
}
module.exports = {
compileParcel
}
if (require.main === module) compileParcel()
+11
View File
@@ -0,0 +1,11 @@
const { compileParcel } = require('./parcel-build')
async function watchParcel () {
return compileParcel({ watch: true })
}
module.exports = {
watchParcel
}
if (require.main === module) watchParcel()
-27
View File
@@ -1,27 +0,0 @@
module.exports = {
/**
* This is the main entry point for your application, it's the first file
* that runs in the main process.
*/
entry: './src/main/main.ts',
// Put your normal webpack config below here
module: {
rules: require('./webpack.rules'),
},
resolve: {
/**
* Determines which file extensions are okay to leave off from
* the ends of require / import paths. Has no impact on what file types
* webpack will process.
*
* For example:
*
* import { logger } from './logger';
*
* Instead of:
*
* import { logger } from './logger.ts';
*/
extensions: [ '.ts', '.tsx', '.js', '.jsx', '.json' ],
}
};
-83
View File
@@ -1,83 +0,0 @@
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');
const rules = require('./webpack.rules');
rules.push({
test: /\.css$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' }
],
});
rules.push({
test: /\.less$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' },
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
],
});
rules.push({
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
});
module.exports = {
// Put your normal webpack config below here
module: {
rules,
},
resolve: {
alias: {
'monaco-editor': 'monaco-editor/esm/vs/editor/editor.api'
},
/**
* Determines which file extensions are okay to leave off from
* the ends of require / import paths. Has no impact on what file types
* webpack will process.
*
* For example:
*
* import { logger } from './logger';
*
* Instead of:
*
* import { logger } from './logger.ts';
*/
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
},
plugins: [
new MonacoWebpackPlugin({
languages: [
'typescript',
'javascript',
'html',
'css'
]
}),
new BundleAnalyzerPlugin(),
new MiniCssExtractPlugin({
filename: "./css/[name].css"
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
externals: {
'es6-promise': 'es6-promise'
}
};
-27
View File
@@ -1,27 +0,0 @@
module.exports = [
// Add support for native node modules
{
test: /\.node$/,
use: 'node-loader',
},
{
test: /\.(m?js|node)$/,
parser: { amd: false },
use: {
loader: '@marshallofsound/webpack-asset-relocator-loader',
options: {
outputAssetBase: 'native_modules',
},
},
},
{
test: /\.tsx?$/,
exclude: /(node_modules|.webpack)/,
loaders: [{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}]
}
];