Compare commits
198
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26e054702a | ||
|
|
16f27132cb | ||
|
|
7613176f69 | ||
|
|
470bea4fca | ||
|
|
363febdb73 | ||
|
|
062ca4e205 | ||
|
|
49053342a5 | ||
|
|
fd582c67a8 | ||
|
|
706b18808b | ||
|
|
a6ae8d5376 | ||
|
|
5034f8c853 | ||
|
|
e916247013 | ||
|
|
c052686de4 | ||
|
|
31aacef131 | ||
|
|
932369b705 | ||
|
|
b835c34786 | ||
|
|
c9e1ba815a | ||
|
|
374a85e2f6 | ||
|
|
4282d0b93d | ||
|
|
549862b3bb | ||
|
|
4691c1b494 | ||
|
|
8125abc657 | ||
|
|
8cba7739f5 | ||
|
|
0b39fe40b1 | ||
|
|
cae078ffe6 | ||
|
|
bfb931435f | ||
|
|
c878f72f2e | ||
|
|
32494c057a | ||
|
|
e732685fff | ||
|
|
d9d5e0cbc9 | ||
|
|
0e1d7b541e | ||
|
|
8a602d94b7 | ||
|
|
155889dbcc | ||
|
|
56ac6662b6 | ||
|
|
adb196549b | ||
|
|
999facf062 | ||
|
|
fa0115f2db | ||
|
|
c0a4c0e48d | ||
|
|
3a5d2205fe | ||
|
|
1083b8f859 | ||
|
|
252d4c1de6 | ||
|
|
0c44d2c9e4 | ||
|
|
5eebc06514 | ||
|
|
eac1423177 | ||
|
|
ae88780281 | ||
|
|
4bf9d48d70 | ||
|
|
df6d4b5a9a | ||
|
|
68202e2b23 | ||
|
|
acd5b35ca1 | ||
|
|
fae1ca3138 | ||
|
|
bd220d2690 | ||
|
|
d31f1016b8 | ||
|
|
3b52be0f79 | ||
|
|
0e11c56f7f | ||
|
|
8019981d1a | ||
|
|
403a58e216 | ||
|
|
3341d0ccaa | ||
|
|
d42e83100a | ||
|
|
50e9883d82 | ||
|
|
f5aedac6a3 | ||
|
|
a3f4aa8ed3 | ||
|
|
9c839aaad8 | ||
|
|
3a7ca8ccd5 | ||
|
|
fef42293bc | ||
|
|
1f9db42c80 | ||
|
|
8e5657ef2d | ||
|
|
a94105dd27 | ||
|
|
cf214dc0d0 | ||
|
|
6dfdf00437 | ||
|
|
b3743a4c73 | ||
|
|
3c0d6c2293 | ||
|
|
89b589fd2e | ||
|
|
42c577988d | ||
|
|
d0e47b6b4d | ||
|
|
f44a127012 | ||
|
|
fa2f5454ce | ||
|
|
1021bb8b2b | ||
|
|
cf4ad36ca2 | ||
|
|
4fe9c627b5 | ||
|
|
262e636e8d | ||
|
|
bbe79ed6e2 | ||
|
|
bc96a5013a | ||
|
|
66ba7b74d5 | ||
|
|
38e5aa98e3 | ||
|
|
a91504b173 | ||
|
|
4685e88c86 | ||
|
|
e0bb82d919 | ||
|
|
7035be3286 | ||
|
|
c961cfadb8 | ||
|
|
7267a2ee88 | ||
|
|
bc03382dc5 | ||
|
|
891c33ba68 | ||
|
|
e7260c74a4 | ||
|
|
ed694a7044 | ||
|
|
14fc77c08c | ||
|
|
f3079648cf | ||
|
|
7272ec7899 | ||
|
|
ac9ac701f7 | ||
|
|
7fce572823 | ||
|
|
6186a80dad | ||
|
|
ade6576285 | ||
|
|
61c58277f3 | ||
|
|
c361d34a6d | ||
|
|
5887a6615a | ||
|
|
7ba4cf5be7 | ||
|
|
0daf872d2b | ||
|
|
75ec8ee9b1 | ||
|
|
183c152e40 | ||
|
|
d6a807ba41 | ||
|
|
24e0abf667 | ||
|
|
eb8261e240 | ||
|
|
84cc07da90 | ||
|
|
b0f3837b47 | ||
|
|
e29664ca45 | ||
|
|
ef36ee39f6 | ||
|
|
73dadb815e | ||
|
|
4945501703 | ||
|
|
77c87fbdeb | ||
|
|
2ef5b42f4a | ||
|
|
9c4bc2ba52 | ||
|
|
80da4e73e3 | ||
|
|
eadd58d149 | ||
|
|
1fc82f8f6d | ||
|
|
9bd7043b2c | ||
|
|
aaeef6fd06 | ||
|
|
1195902bd5 | ||
|
|
c022d0f33d | ||
|
|
996ae775f3 | ||
|
|
ed44dda8fb | ||
|
|
effba13e73 | ||
|
|
65886c3b52 | ||
|
|
8bd4dd366a | ||
|
|
4e19578e34 | ||
|
|
c7fd0bc95e | ||
|
|
29e2f03b1a | ||
|
|
4cd6f330da | ||
|
|
79521eac7c | ||
|
|
2736499d68 | ||
|
|
1cbb781e8d | ||
|
|
e849a49f6f | ||
|
|
0114d2c1eb | ||
|
|
365537012f | ||
|
|
3e435cb64d | ||
|
|
6981382793 | ||
|
|
f293ba84d1 | ||
|
|
55cf507a96 | ||
|
|
e71c2439cd | ||
|
|
9bd253951c | ||
|
|
eda08c9aea | ||
|
|
9414f66893 | ||
|
|
f4725b5dd8 | ||
|
|
fd8be47494 | ||
|
|
2cebe2ca7c | ||
|
|
74135bab67 | ||
|
|
a856340d01 | ||
|
|
bd8939c358 | ||
|
|
9d3f3d61eb | ||
|
|
bdeaffcb7d | ||
|
|
344e1df375 | ||
|
|
d5b52dcae7 | ||
|
|
0faeb24761 | ||
|
|
284b579d0c | ||
|
|
5a1c24cdda | ||
|
|
024d48a273 | ||
|
|
32155d652d | ||
|
|
874a8bb0f0 | ||
|
|
73470640c7 | ||
|
|
679f731f4c | ||
|
|
a41ad11f8c | ||
|
|
7cc6d38dc2 | ||
|
|
1e6ee7ab37 | ||
|
|
c50c31ad80 | ||
|
|
5f8557fb13 | ||
|
|
47b20fa3e4 | ||
|
|
b5c8ed60a9 | ||
|
|
5e3d382b85 | ||
|
|
fed0107dd1 | ||
|
|
ca9b9c633a | ||
|
|
fb4c6b3f33 | ||
|
|
aa51c0cc5c | ||
|
|
e5236c5500 | ||
|
|
321f7a8bbc | ||
|
|
1ff7a3fe16 | ||
|
|
a1c74c018d | ||
|
|
5df39ea51a | ||
|
|
8a9591be9f | ||
|
|
3cc8fd41f6 | ||
|
|
25df3dd989 | ||
|
|
abe7bdab23 | ||
|
|
16bea01ba5 | ||
|
|
7417a82931 | ||
|
|
4c41db8595 | ||
|
|
5da87ffca3 | ||
|
|
919e9cb6b6 | ||
|
|
14279283d6 | ||
|
|
77a98112a1 | ||
|
|
08fb3f52bf | ||
|
|
b37ba895e5 |
+1
-1
@@ -4,7 +4,7 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
dist: trusty
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode10
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
Electron Fiddle lets you create and play with small Electron experiments. It
|
||||
greets you with a quick-start template after opening – change a few things,
|
||||
choose the version of Electron you want to run it with, and play around. Then,
|
||||
save your Fiddle either as a GitHub Gist or to a local folder. Once pushed to
|
||||
GitHub, anyone can quickly try your Fiddle out by just entering it in the
|
||||
save your Fiddle either as a GitHub Gist or to a local folder. Once published
|
||||
on GitHub, anyone can quickly try your Fiddle out by just entering it in the
|
||||
address bar.
|
||||
|
||||
<h3 align="center">
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "12"
|
||||
|
||||
init:
|
||||
- git config --global core.symlinks true
|
||||
|
||||
+36
-3
@@ -7,7 +7,7 @@ const packageJson = require('./package.json')
|
||||
const { version } = packageJson
|
||||
const iconDir = path.resolve(__dirname, 'assets', 'icons')
|
||||
|
||||
module.exports = {
|
||||
const config = {
|
||||
hooks: {
|
||||
generateAssets: require('./tools/generateAssets')
|
||||
},
|
||||
@@ -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: [
|
||||
@@ -90,8 +95,36 @@ module.exports = {
|
||||
name: 'fiddle'
|
||||
},
|
||||
draft: true,
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function notarizeMaybe() {
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.CI) {
|
||||
console.log(`Not in CI, skipping notarization`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
|
||||
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
|
||||
return;
|
||||
}
|
||||
|
||||
config.packagerConfig.osxNotarize = {
|
||||
appBundleId: 'com.electron.fiddle',
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASSWORD,
|
||||
ascProvider: 'LT94ZKYDCJ'
|
||||
}
|
||||
}
|
||||
|
||||
notarizeMaybe()
|
||||
|
||||
// Finally, export it
|
||||
module.exports = config
|
||||
|
||||
Generated
+5214
-4061
File diff suppressed because it is too large
Load Diff
+50
-50
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "electron-fiddle",
|
||||
"productName": "Electron Fiddle",
|
||||
"version": "0.7.0",
|
||||
"version": "0.11.1",
|
||||
"description": "The easiest way to get started with Electron",
|
||||
"repository": "https://github.com/electron/fiddle",
|
||||
"main": "./dist/src/main/main",
|
||||
@@ -36,9 +36,9 @@
|
||||
"forge": "./forge.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.15.1",
|
||||
"@blueprintjs/select": "^3.8.0",
|
||||
"@octokit/rest": "^16.25.6",
|
||||
"@blueprintjs/core": "^3.19.1",
|
||||
"@blueprintjs/select": "^3.11.1",
|
||||
"@octokit/rest": "^16.34.0",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"electron-default-menu": "^1.0.1",
|
||||
@@ -47,62 +47,62 @@
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"extract-zip": "^1.6.7",
|
||||
"fix-path": "^2.1.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"mobx": "^5.9.4",
|
||||
"mobx-react": "^5.4.4",
|
||||
"monaco-editor": "^0.17.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"mobx": "^5.14.2",
|
||||
"mobx-react": "^6.1.4",
|
||||
"monaco-editor": "^0.18.1",
|
||||
"monaco-loader": "1.0.0",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-mosaic-component": "^2.1.0",
|
||||
"semver": "^6.0.0",
|
||||
"namor": "^1.1.3",
|
||||
"react": "^16.11.0",
|
||||
"react-dom": "^16.11.0",
|
||||
"react-mosaic-component": "^3.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"tmp": "0.1.0",
|
||||
"tslib": "^1.9.3",
|
||||
"update-electron-app": "^1.3.0"
|
||||
"tslib": "^1.10.0",
|
||||
"update-electron-app": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@electron-forge/cli": "^6.0.0-beta.34",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.39",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.34",
|
||||
"@babel/core": "^7.6.4",
|
||||
"@electron-forge/cli": "^6.0.0-beta.46",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.46",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.46",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.46",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.46",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.46",
|
||||
"@types/builtin-modules": "^3.1.1",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/enzyme": "^3.9.2",
|
||||
"@types/fs-extra": "^7.0.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/enzyme": "^3.10.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/log-symbols": "^3.0.0",
|
||||
"@types/node": "^10.11.1",
|
||||
"@types/react": "^16.8.17",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-dom": "^16.9.3",
|
||||
"@types/semver": "^6.0.2",
|
||||
"@types/tmp": "0.1.0",
|
||||
"chokidar": "^2.1.6",
|
||||
"coveralls": "^3.0.3",
|
||||
"electron": "5.0.1",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.13.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"jest": "^24.8.0",
|
||||
"chokidar": "^3.2.2",
|
||||
"coveralls": "^3.0.7",
|
||||
"electron": "7.1.3",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"jest": "^24.9.0",
|
||||
"jest-fetch-mock": "^2.1.2",
|
||||
"less": "^3.9.0",
|
||||
"less": "^3.10.3",
|
||||
"log-symbols": "^3.0.0",
|
||||
"node-abi": "^2.8.0",
|
||||
"node-abi": "^2.12.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"rimraf": "^2.6.3",
|
||||
"standard": "^12.0.1",
|
||||
"stylelint": "^10.0.1",
|
||||
"stylelint-config-standard": "^18.3.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.16.0",
|
||||
"tslint-microsoft-contrib": "^6.1.1",
|
||||
"tslint-react": "^4.0.0",
|
||||
"typescript": "^3.4.5"
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"react-test-renderer": "^16.11.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"standard": "^14.3.1",
|
||||
"stylelint": "^11.1.1",
|
||||
"stylelint-config-standard": "^19.0.0",
|
||||
"ts-jest": "^24.1.0",
|
||||
"tslint": "^5.20.0",
|
||||
"tslint-microsoft-contrib": "^6.2.0",
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -24,6 +24,7 @@ export interface EditorValues {
|
||||
main: string;
|
||||
renderer: string;
|
||||
html: string;
|
||||
preload: string;
|
||||
package?: string;
|
||||
}
|
||||
|
||||
@@ -33,9 +34,9 @@ export interface ElectronVersion extends NpmVersion {
|
||||
}
|
||||
|
||||
export interface SetFiddleOptions {
|
||||
values: EditorValues;
|
||||
filePath?: string;
|
||||
templateName?: string;
|
||||
gistId?: string;
|
||||
}
|
||||
|
||||
export interface OutputEntry {
|
||||
@@ -63,7 +64,8 @@ export interface Templates {
|
||||
export const enum EditorId {
|
||||
'main' = 'main',
|
||||
'renderer' = 'renderer',
|
||||
'html' = 'html'
|
||||
'html' = 'html',
|
||||
'preload' = 'preload'
|
||||
}
|
||||
|
||||
// Panels that can show up as a mosaic
|
||||
@@ -73,7 +75,7 @@ export const enum PanelId {
|
||||
|
||||
export type MosaicId = EditorId | PanelId;
|
||||
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.html ];
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html ];
|
||||
export const ALL_PANELS = [ PanelId.docsDemo ];
|
||||
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
|
||||
|
||||
|
||||
+13
-3
@@ -17,13 +17,20 @@ export enum IpcEvents {
|
||||
FS_SAVE_FIDDLE_DIALOG = 'FS_SAVE_FIDDLE_DIALOG',
|
||||
FS_SAVE_FIDDLE_ERROR = 'FS_SAVE_FIDDLE_ERROR',
|
||||
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
|
||||
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR'
|
||||
SHOW_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
|
||||
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
|
||||
CLEAR_CONSOLE = 'CLEAR_CONSOLE',
|
||||
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
|
||||
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
|
||||
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
|
||||
}
|
||||
|
||||
export const ipcMainEvents = [
|
||||
IpcEvents.FS_SAVE_FIDDLE_DIALOG,
|
||||
IpcEvents.FS_SAVE_FIDDLE,
|
||||
IpcEvents.SHOW_WARNING_DIALOG
|
||||
IpcEvents.SHOW_WARNING_DIALOG,
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG,
|
||||
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
|
||||
];
|
||||
|
||||
export const ipcRendererEvents = [
|
||||
@@ -42,7 +49,10 @@ export const ipcRendererEvents = [
|
||||
IpcEvents.FS_SAVE_FIDDLE_AS,
|
||||
IpcEvents.FS_SAVE_FIDDLE_FORGE,
|
||||
IpcEvents.FS_SAVE_FIDDLE_GIST,
|
||||
IpcEvents.SHOW_WELCOME_TOUR
|
||||
IpcEvents.SHOW_WELCOME_TOUR,
|
||||
IpcEvents.CLEAR_CONSOLE,
|
||||
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
|
||||
IpcEvents.BISECT_COMMANDS_TOGGLE
|
||||
];
|
||||
|
||||
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
button.version-chooser.bp3-button {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
header {
|
||||
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);
|
||||
//border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: @background-3;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
@@ -20,6 +19,7 @@ header {
|
||||
select {
|
||||
-webkit-app-region: no-drag;
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -27,6 +27,13 @@ header {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
|
||||
|
||||
@@ -7,20 +7,15 @@
|
||||
color: var(--text-color-1);
|
||||
background: var(--background-2);
|
||||
box-shadow: inset 0 0 100px 100px rgba(0, 0, 0, 0.15);
|
||||
border-top: @border;
|
||||
top: 10vh;
|
||||
padding: 0;
|
||||
padding-top: 10px;
|
||||
padding-left: 10px;
|
||||
height: 0;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
transition: height 0.2s linear, padding 0.2s linear;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
&.showing {
|
||||
border-top: @border;
|
||||
padding-top: 10px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
white-space: pre;
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -15,6 +15,11 @@ export function getRunItems(): Array<MenuItemConstructorOptions> {
|
||||
label: 'Run Fiddle',
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN)
|
||||
},
|
||||
{
|
||||
id: 'clear_console',
|
||||
label: 'Clear Console',
|
||||
click: () => ipcMainManager.send(IpcEvents.CLEAR_CONSOLE)
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+35
-1
@@ -1,4 +1,4 @@
|
||||
import { dialog } from 'electron';
|
||||
import { dialog, IpcMainEvent } from 'electron';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { ipcMainManager } from './ipc';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
@@ -12,6 +12,15 @@ export function setupDialogs() {
|
||||
ipcMainManager.on(IpcEvents.SHOW_WARNING_DIALOG, (_event, args) => {
|
||||
showWarningDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_CONFIRMATION_DIALOG, (_event, args) => {
|
||||
showConfirmationDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
|
||||
await showOpenDialog(event);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,3 +34,28 @@ function showWarningDialog(args: Electron.MessageBoxOptions) {
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog
|
||||
*
|
||||
* @param {Electron.MessageBoxOptions} args
|
||||
*/
|
||||
function showConfirmationDialog(args: Electron.MessageBoxOptions) {
|
||||
dialog.showMessageBox(getOrCreateMainWindow(), {
|
||||
type: 'warning',
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
async function showOpenDialog(event: IpcMainEvent) {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Folder',
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.reply(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, [filePaths[0]]);
|
||||
}
|
||||
|
||||
+21
-21
@@ -19,41 +19,41 @@ export function setupFileListeners() {
|
||||
* Shows the "Open Fiddle" dialog and forwards
|
||||
* the path to the renderer
|
||||
*/
|
||||
export function showOpenDialog() {
|
||||
dialog.showOpenDialog({
|
||||
export async function showOpenDialog() {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Fiddle',
|
||||
properties: ['openDirectory']
|
||||
}, (filePaths) => {
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the "Save Fiddle" dialog and forwards
|
||||
* the path to the renderer
|
||||
*/
|
||||
export function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
export async function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
// We want to save to a folder, so we'll use an open dialog here
|
||||
dialog.showOpenDialog({
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
buttonLabel: 'Save here',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
title: `Save Fiddle${as ? ` as ${as}` : ''}`
|
||||
}, async (filePaths) => {
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Asked to save to ${filePaths[0]}`);
|
||||
|
||||
// Let's confirm real quick if we want this
|
||||
if (await ensureSaveTargetEmpty(filePaths[0])) {
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Asked to save to ${filePaths[0]}`);
|
||||
|
||||
// Let's confirm real quick if we want this
|
||||
if (await ensureSaveTargetEmpty(filePaths[0])) {
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ import { isDevMode } from '../utils/devmode';
|
||||
* Is this the first run of Fiddle? If so, perform
|
||||
* tasks that we only want to do in this case.
|
||||
*/
|
||||
export function onFirstRunMaybe() {
|
||||
export async function onFirstRunMaybe() {
|
||||
if (isFirstRun()) {
|
||||
promptMoveToApplicationsFolder();
|
||||
await promptMoveToApplicationsFolder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ export function onFirstRunMaybe() {
|
||||
* Ask the user if the app should be moved to the
|
||||
* applications folder.
|
||||
*/
|
||||
function promptMoveToApplicationsFolder(): void {
|
||||
async function promptMoveToApplicationsFolder(): Promise<void> {
|
||||
if (process.platform !== 'darwin') return;
|
||||
if (isDevMode() || app.isInApplicationsFolder()) return;
|
||||
|
||||
dialog.showMessageBox({
|
||||
const { response } = await dialog.showMessageBox({
|
||||
type: 'question',
|
||||
buttons: ['Move to Applications Folder', 'Do Not Move'],
|
||||
defaultId: 0,
|
||||
message: 'Move to Applications Folder?',
|
||||
}, (response) => {
|
||||
if (response === 0) {
|
||||
app.moveToApplicationsFolder();
|
||||
}
|
||||
});
|
||||
|
||||
if (response === 0) {
|
||||
app.moveToApplicationsFolder();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
|
||||
@@ -22,8 +23,9 @@ export class IpcMainManager extends EventEmitter {
|
||||
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
|
||||
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.Event) => {
|
||||
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;
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import { getOrCreateMainWindow } from './windows';
|
||||
* the method that takes care of booting the application.
|
||||
*/
|
||||
export async function onReady() {
|
||||
onFirstRunMaybe();
|
||||
await onFirstRunMaybe();
|
||||
if (!isDevMode()) process.env.NODE_ENV = 'production';
|
||||
|
||||
getOrCreateMainWindow();
|
||||
@@ -68,7 +68,7 @@ export function main() {
|
||||
}
|
||||
|
||||
// Set the app's name
|
||||
app.setName('Electron Fiddle');
|
||||
app.name = 'Electron Fiddle';
|
||||
|
||||
// Ensure that there's only ever one Fiddle running
|
||||
listenForProtocolHandler();
|
||||
|
||||
+14
-9
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +206,7 @@ function getFileMenu(): MenuItemConstructorOptions {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Save to Gist',
|
||||
label: 'Publish to Gist',
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_SAVE_FIDDLE_GIST),
|
||||
},
|
||||
{
|
||||
@@ -248,14 +248,19 @@ export function setupMenu() {
|
||||
// Tweak "View" menu
|
||||
if (label === 'View' && isSubmenu(item.submenu)) {
|
||||
item.submenu = item.submenu.filter((subItem) => subItem.label !== 'Toggle Developer Tools'); // Remove "Toggle Developer Tools"
|
||||
item.submenu.push({ type: 'separator' }, { role: 'resetzoom' }, { role: 'zoomin' }, { role: 'zoomout' }); // Add zooming actions
|
||||
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
|
||||
item.submenu.push({ type: 'separator' }, {
|
||||
label: 'Toggle Soft Wrap',
|
||||
click: () => ipcMainManager.send(IpcEvents.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',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -4,6 +4,7 @@ import * as path from 'path';
|
||||
import * as nodeUrl from 'url';
|
||||
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { ipcMainManager } from './ipc';
|
||||
|
||||
const PROTOCOL = 'electron-fiddle';
|
||||
@@ -65,6 +66,12 @@ export const scanArgv = (argv: Array<string>) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const scanNpmArgv = (argv: string) => {
|
||||
const parsedArgv = JSON.parse(argv);
|
||||
const { original: args } = parsedArgv;
|
||||
scanArgv(args);
|
||||
};
|
||||
|
||||
export const listenForProtocolHandler = () => {
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) app.quit();
|
||||
@@ -75,7 +82,10 @@ export const listenForProtocolHandler = () => {
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// pass protocol URL via npm start args in dev mode
|
||||
if (isDevMode() && process.env.npm_config_argv) {
|
||||
scanNpmArgv(process.env.npm_config_argv);
|
||||
} else if (process.platform === 'win32') {
|
||||
scanArgv(process.argv);
|
||||
}
|
||||
};
|
||||
|
||||
+75
-38
@@ -2,13 +2,20 @@ import { when } from 'mobx';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ALL_EDITORS, EditorId, EditorValues } from '../interfaces';
|
||||
import {
|
||||
ALL_EDITORS,
|
||||
EditorId,
|
||||
EditorValues,
|
||||
SetFiddleOptions
|
||||
} from '../interfaces';
|
||||
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { updateEditorLayout } from '../utils/editor-layout';
|
||||
import { getEditorValue } from '../utils/editor-value';
|
||||
import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
|
||||
import { getTitle } from '../utils/get-title';
|
||||
import { isEditorBackup } from '../utils/type-checks';
|
||||
import { FileManager } from './file-manager';
|
||||
import { RemoteLoader } from './remote-loader';
|
||||
import { Runner } from './runner';
|
||||
import { appState } from './state';
|
||||
import { getTheme } from './themes';
|
||||
@@ -25,32 +32,29 @@ export class App {
|
||||
public monaco: typeof MonacoType | null = null;
|
||||
public state = appState;
|
||||
public fileManager = new FileManager(appState);
|
||||
public remoteLoader = new RemoteLoader(appState);
|
||||
public runner = new Runner(appState);
|
||||
public touchBarManager: TouchBarManager | undefined;
|
||||
|
||||
constructor() {
|
||||
this.getValues = this.getValues.bind(this);
|
||||
this.setValues = this.setValues.bind(this);
|
||||
this.getEditorValues = this.getEditorValues.bind(this);
|
||||
this.setEditorValues = this.setEditorValues.bind(this);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
this.touchBarManager = new TouchBarManager(appState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values on all three editors.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {warn} warn - Should we warn before overwriting unsaved data?
|
||||
*/
|
||||
public async setValues(values: Partial<EditorValues>, warn: boolean = true): Promise<boolean> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
if (this.state.isUnsaved && warn) {
|
||||
public async replaceFiddle(
|
||||
editorValues: Partial<EditorValues>,
|
||||
{ filePath, gistId, templateName }: Partial<SetFiddleOptions>
|
||||
) {
|
||||
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
|
||||
if (this.state.isUnsaved) {
|
||||
this.state.setWarningDialogTexts({
|
||||
label: `Opening this Fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes'
|
||||
});
|
||||
this.state.isWarningDialogShowing = true;
|
||||
await when(() => !this.state.isWarningDialogShowing);
|
||||
|
||||
@@ -59,31 +63,58 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ALL_EDITORS) {
|
||||
const editor = fiddle.editors[name];
|
||||
const backup = this.state.closedPanels[name];
|
||||
// set values once prompt approves
|
||||
await this.setEditorValues(editorValues);
|
||||
|
||||
if (isEditorBackup(backup) && backup.model && values[name]) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
} else if (editor && editor.setValue && values[name]) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
}
|
||||
}
|
||||
document.title = getTitle(this.state);
|
||||
this.state.gistId = gistId || '';
|
||||
this.state.localPath = filePath;
|
||||
this.state.templateName = templateName;
|
||||
|
||||
// once loaded, we have a "saved" state
|
||||
this.state.isUnsaved = false;
|
||||
this.setupUnsavedOnChangeListener();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values on all three editors.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {warn} warn - Should we warn before overwriting unsaved data?
|
||||
*/
|
||||
public async setEditorValues(values: Partial<EditorValues>): Promise<void> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
for (const name of ALL_EDITORS) {
|
||||
const editor = fiddle.editors[name];
|
||||
const backup = this.state.closedPanels[name];
|
||||
|
||||
if (typeof values[name] !== 'undefined') {
|
||||
if (isEditorBackup(backup) && backup.model) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
} else if (editor && editor.setValue) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values on all three editors.
|
||||
*
|
||||
* @returns {EditorValues}
|
||||
*/
|
||||
public async getValues(options?: PackageJsonOptions): Promise<EditorValues> {
|
||||
public async getEditorValues(
|
||||
options?: PackageJsonOptions
|
||||
): Promise<EditorValues> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
@@ -93,10 +124,11 @@ export class App {
|
||||
const values: EditorValues = {
|
||||
html: getEditorValue(EditorId.html),
|
||||
main: getEditorValue(EditorId.main),
|
||||
renderer: getEditorValue(EditorId.renderer),
|
||||
preload: getEditorValue(EditorId.preload),
|
||||
renderer: getEditorValue(EditorId.renderer)
|
||||
};
|
||||
|
||||
if (options && options.include !== false) {
|
||||
if (options && options.include !== false) {
|
||||
values.package = await getPackageJson(this.state, values, options);
|
||||
}
|
||||
|
||||
@@ -112,16 +144,18 @@ export class App {
|
||||
|
||||
const React = await import('react');
|
||||
const { render } = await import('react-dom');
|
||||
const { Header } = await import('./components/header');
|
||||
const { Dialogs } = await import('./components/dialogs');
|
||||
const { Editors } = await import('./components/editors');
|
||||
const { OutputEditorsWrapper } = await import(
|
||||
'./components/output-editors-wrapper'
|
||||
);
|
||||
const { Header } = await import('./components/header');
|
||||
|
||||
const className = `${process.platform} container`;
|
||||
const app = (
|
||||
<div className={className}>
|
||||
<Header appState={this.state} />
|
||||
<Dialogs appState={this.state} />
|
||||
<Editors appState={this.state} />
|
||||
<Header appState={this.state} />
|
||||
<OutputEditorsWrapper appState={this.state} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -131,7 +165,7 @@ export class App {
|
||||
|
||||
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
|
||||
|
||||
// Todo: A timer here is terrible. Let's fix this
|
||||
// TODO: A timer here is terrible. Let's fix this
|
||||
// and ensure we actually do it once Editors have mounted.
|
||||
setTimeout(() => {
|
||||
this.setupUnsavedOnChangeListener();
|
||||
@@ -161,7 +195,9 @@ export class App {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async setupTheme(): Promise<void> {
|
||||
const tag: HTMLStyleElement | null = document.querySelector('style#fiddle-theme');
|
||||
const tag: HTMLStyleElement | null = document.querySelector(
|
||||
'style#fiddle-theme'
|
||||
);
|
||||
const theme = await getTheme(this.state.theme);
|
||||
|
||||
if (tag && theme.css) {
|
||||
@@ -185,6 +221,7 @@ export class App {
|
||||
}
|
||||
|
||||
window.ElectronFiddle = window.ElectronFiddle || {};
|
||||
window.ElectronFiddle.contentChangeListeners = window.ElectronFiddle.contentChangeListeners || [];
|
||||
window.ElectronFiddle.contentChangeListeners =
|
||||
window.ElectronFiddle.contentChangeListeners || [];
|
||||
window.ElectronFiddle.app = window.ElectronFiddle.app || new App();
|
||||
window.ElectronFiddle.app.setup();
|
||||
|
||||
+45
-12
@@ -4,6 +4,7 @@ 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';
|
||||
|
||||
/**
|
||||
* The binary manager takes care of downloading Electron versions
|
||||
@@ -19,31 +20,48 @@ export class BinaryManager {
|
||||
* four times before giving up if an error occurs.
|
||||
*
|
||||
* @param {string} iVersion
|
||||
* @param {number} iteration
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async remove(iVersion: string, i: number = 0): Promise<void> {
|
||||
public async remove(iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
let isDeleted = false;
|
||||
|
||||
try {
|
||||
// utility to re-run removal functions upon failure
|
||||
// due to windows filesystem lockfile jank
|
||||
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
|
||||
try {
|
||||
await func();
|
||||
} catch (error) {
|
||||
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
|
||||
if (counter < 4) {
|
||||
console.log(`Binary Manager: Trying again to run ${func.name}`);
|
||||
await rerunner(func, counter + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const binaryCleaner = async () => {
|
||||
if (await this.getIsDownloaded(version)) {
|
||||
// This is necessary since we're messing with .asar files inside
|
||||
// the Electron binaries. Electron, powering Fiddle, will try to
|
||||
// "correct" our calls, but we don't want that right here.
|
||||
process.noAsar = true;
|
||||
|
||||
await fs.remove(this.getDownloadPath(version));
|
||||
|
||||
process.noAsar = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Binary Manager: Tried to remove ${version}, but failed`, error);
|
||||
|
||||
if (i < 3) {
|
||||
console.log(`Binary Manager: Trying again`);
|
||||
return this.remove(version, i + 1);
|
||||
isDeleted = true;
|
||||
}
|
||||
};
|
||||
|
||||
const typeDefsCleaner = async () => {
|
||||
await this.removeTypeDefsForVersion(version);
|
||||
};
|
||||
|
||||
await rerunner(binaryCleaner);
|
||||
|
||||
if (isDeleted) {
|
||||
await rerunner(typeDefsCleaner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +107,7 @@ export class BinaryManager {
|
||||
} catch (error) {
|
||||
console.warn(`Failure while unzipping ${version}`, error);
|
||||
|
||||
// Todo: Handle this case
|
||||
// TODO: Handle this case
|
||||
}
|
||||
|
||||
this.state[version] = 'ready';
|
||||
@@ -160,6 +178,20 @@ export class BinaryManager {
|
||||
return fs.existsSync(expectedPath);
|
||||
}
|
||||
|
||||
public async removeTypeDefsForVersion(version: string) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const _version = normalizeVersion(version);
|
||||
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
|
||||
|
||||
if (fs.existsSync(typeDefsDir)) {
|
||||
try {
|
||||
await fs.remove(typeDefsDir);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for a given Electron version
|
||||
*
|
||||
@@ -170,6 +202,7 @@ export class BinaryManager {
|
||||
return path.join(USER_DATA_PATH, 'electron-bin', version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unzips an electron package so that we can actually use it.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import { Button, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import { reaction, when } from 'mobx';
|
||||
import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { EditorId, EditorValues } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
|
||||
import { getTitle } from '../../utils/get-title';
|
||||
import { idFromUrl, urlFromId } from '../../utils/gist';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { getContent } from '../content';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
@@ -20,26 +15,32 @@ export interface AddressBarProps {
|
||||
|
||||
export interface AddressBarState {
|
||||
value: string;
|
||||
loaders: {
|
||||
gist: any;
|
||||
example: any;
|
||||
};
|
||||
}
|
||||
|
||||
@observer
|
||||
export class AddressBar extends React.Component<AddressBarProps, AddressBarState> {
|
||||
constructor(props: AddressBarProps) {
|
||||
super(props);
|
||||
|
||||
this.fetchExampleAndLoad = this.fetchExampleAndLoad.bind(this);
|
||||
this.fetchGistAndLoad = this.fetchGistAndLoad.bind(this);
|
||||
this.loadFiddleFromGist = this.loadFiddleFromGist.bind(this);
|
||||
this.loadFiddleFromElectronExample = this.loadFiddleFromElectronExample.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.verifyRemoteLoad = this.verifyRemoteLoad.bind(this);
|
||||
|
||||
const { gistId } = this.props.appState;
|
||||
const value = gistId ? urlFromId(gistId) : '';
|
||||
|
||||
this.state = { value };
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
|
||||
this.state = {
|
||||
value,
|
||||
loaders: {
|
||||
gist: remoteLoader.loadFiddleFromGist.bind(remoteLoader),
|
||||
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +60,9 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public submit() {
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
if (this.state.value) {
|
||||
this.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
remoteLoader.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,48 +70,20 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
* Once the component mounts, we'll subscribe to gistId changes
|
||||
*/
|
||||
public componentDidMount() {
|
||||
const { appState } = this.props;
|
||||
const { loaders } = this.state;
|
||||
reaction(
|
||||
() => this.props.appState.gistId,
|
||||
() => appState.gistId,
|
||||
(gistId: string) => this.setState({ value: urlFromId(gistId) })
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies from the user that we should be loading this fiddle
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
appState.setWarningDialogTexts({
|
||||
label: `Are you sure you sure you want to load this ${what}? Only load and run it if you trust the source`
|
||||
});
|
||||
appState.isWarningDialogShowing = true;
|
||||
await when(() => !appState.isWarningDialogShowing);
|
||||
|
||||
return !!appState.warningDialogLastResult;
|
||||
const { loaders } = this.state;
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,85 +95,6 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
this.setState({ value: event.target.value });
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
try {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
// TODO: Maybe we should fetch the package.json for the given ref to use the correct version of Electron?
|
||||
const folder = await octo.repos.getContents({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
|
||||
const values = {
|
||||
html: await getContent(EditorId.html, appState.version),
|
||||
renderer: await getContent(EditorId.renderer, appState.version),
|
||||
main: await getContent(EditorId.main, appState.version),
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
if (!Array.isArray(folder.data)) {
|
||||
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(loaders);
|
||||
|
||||
return this.handleLoadingSuccess(values, '');
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fiddle
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const gist = await octo.gists.get({ gist_id: gistId });
|
||||
|
||||
return this.handleLoadingSuccess({
|
||||
html: gist.data.files[INDEX_HTML_NAME].content,
|
||||
main: gist.data.files[MAIN_JS_NAME].content,
|
||||
renderer: gist.data.files[RENDERER_JS_NAME].content,
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
|
||||
return (
|
||||
<Button
|
||||
@@ -212,68 +107,25 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isUnsaved } = 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 });
|
||||
|
||||
return (
|
||||
<form className={className} onSubmit={this.handleSubmit}>
|
||||
<InputGroup
|
||||
key='addressbar'
|
||||
leftIcon='geosearch'
|
||||
intent={isCorrect || !value ? undefined : Intent.DANGER}
|
||||
onChange={this.handleChange}
|
||||
placeholder='https://gist.github.com/...'
|
||||
value={value}
|
||||
rightElement={this.renderLoadButton(isCorrect)}
|
||||
/>
|
||||
<fieldset disabled={isPublishing}>
|
||||
<InputGroup
|
||||
key='addressbar'
|
||||
leftIcon='geosearch'
|
||||
intent={isCorrect || !value ? undefined : Intent.DANGER}
|
||||
onChange={this.handleChange}
|
||||
placeholder='https://gist.github.com/...'
|
||||
value={value}
|
||||
rightElement={this.renderLoadButton(isCorrect)}
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub succeeded, let's move on.
|
||||
*
|
||||
* @private
|
||||
* @param {Partial<EditorValues>} values
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
appState.setWarningDialogTexts({
|
||||
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues(values);
|
||||
|
||||
document.title = getTitle(appState);
|
||||
appState.gistId = gistId;
|
||||
appState.localPath = undefined;
|
||||
appState.templateName = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub failed - this method handles this case
|
||||
* gracefully.
|
||||
*
|
||||
* @private
|
||||
* @param {Error} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
|
||||
this.props.appState.toogleWarningDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ export interface PublishButtonProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface PublishButtonState {
|
||||
isPublishing: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "publish" button takes care of logging you in.
|
||||
*
|
||||
@@ -25,11 +21,9 @@ export interface PublishButtonState {
|
||||
* @extends {React.Component<PublishButtonProps, PublishButtonState>}
|
||||
*/
|
||||
@observer
|
||||
export class PublishButton extends React.Component<PublishButtonProps, PublishButtonState> {
|
||||
export class PublishButton extends React.Component<PublishButtonProps> {
|
||||
constructor(props: PublishButtonProps) {
|
||||
super(props);
|
||||
|
||||
this.state = { isPublishing: false };
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.publishFiddle = this.publishFiddle.bind(this);
|
||||
this.setPrivate = this.setPrivate.bind(this);
|
||||
@@ -71,12 +65,13 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
* and update all related properties in the app state.
|
||||
*/
|
||||
public async publishFiddle(): Promise<void> {
|
||||
this.setState({ isPublishing: true });
|
||||
const { appState } = this.props;
|
||||
appState.isPublishing = true;
|
||||
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const { gitHubPublishAsPublic } = this.props.appState;
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getValues(options);
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(options);
|
||||
|
||||
try {
|
||||
const gist = await octo.gists.create({
|
||||
@@ -95,7 +90,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
},
|
||||
} as any); // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
|
||||
|
||||
this.props.appState.gistId = gist.data.id;
|
||||
appState.gistId = gist.data.id;
|
||||
|
||||
console.log(`Publish Button: Publishing done`, { gist });
|
||||
} catch (error) {
|
||||
@@ -109,7 +104,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
|
||||
}
|
||||
|
||||
this.setState({ isPublishing: false });
|
||||
appState.isPublishing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +127,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
|
||||
public render() {
|
||||
const { gitHubPublishAsPublic } = this.props.appState;
|
||||
const { isPublishing } = this.state;
|
||||
const { isPublishing } = this.props.appState;
|
||||
|
||||
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
|
||||
const privacyMenu = (
|
||||
@@ -153,24 +148,24 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonGroup className='button-publish'>
|
||||
<Popover
|
||||
content={privacyMenu}
|
||||
position={Position.BOTTOM}
|
||||
disabled={isPublishing}
|
||||
>
|
||||
<Button
|
||||
icon={privacyIcon}
|
||||
/>
|
||||
</Popover>
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
loading={isPublishing}
|
||||
disabled={isPublishing}
|
||||
icon='upload'
|
||||
text={isPublishing ? 'Publishing...' : 'Publish'}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<fieldset disabled={isPublishing}>
|
||||
<ButtonGroup className='button-publish'>
|
||||
<Popover
|
||||
content={privacyMenu}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
icon={privacyIcon}
|
||||
/>
|
||||
</Popover>
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
loading={isPublishing}
|
||||
icon='upload'
|
||||
text={isPublishing ? 'Publishing...' : 'Publish'}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { Button, Dialog, FileInput } from '@blueprintjs/core';
|
||||
import { shell } from 'electron';
|
||||
import * as fsType from 'fs-extra';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import { getTheme, THEMES_PATH } from '../themes';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { defaultDark, LoadedFiddleTheme } from '../themes-defaults';
|
||||
|
||||
export interface AddThemeDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface AddThemeDialogState {
|
||||
file?: File;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "add monaco theme" dialog allows users to add custom editor themes.
|
||||
*
|
||||
* @class AddThemeDialog
|
||||
* @extends {React.Component<AddThemeDialogProps, AddThemeDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThemeDialogState> {
|
||||
public resetState = { file: undefined };
|
||||
|
||||
constructor(props: AddThemeDialogProps) {
|
||||
super(props);
|
||||
this.state = this.resetState;
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change of the file input
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
|
||||
const { files } = event.target as any;
|
||||
const file = files && files[0] ? files[0] : undefined;
|
||||
|
||||
this.setState({ file });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of the dialog
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { file } = this.state;
|
||||
const defaultTheme = !!this.props.appState.theme ? await getTheme(this.props.appState.theme) : defaultDark;
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const editor = fsType.readJSONSync(file.path);
|
||||
if (!editor.base && !editor.rules) throw Error('File does not match specifications'); // has to have these attributes
|
||||
defaultTheme.editor = editor as Partial<MonacoType.editor.IStandaloneThemeData>;
|
||||
const newTheme = defaultTheme;
|
||||
const name = editor.name ? editor.name : file.name;
|
||||
await this.createNewThemeFromMonaco(name, newTheme);
|
||||
} catch (error) {
|
||||
this.props.appState.setWarningDialogTexts({label: `Error: ${error}, please pick a different file.`});
|
||||
this.props.appState.isWarningDialogShowing = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
public async createNewThemeFromMonaco(name: string, newTheme: LoadedFiddleTheme): Promise<boolean> {
|
||||
if (!name) return false;
|
||||
const themePath = path.join(THEMES_PATH, `${name}`);
|
||||
|
||||
await fsType.outputJSON(themePath, {
|
||||
...newTheme,
|
||||
name,
|
||||
}, {spaces: 2});
|
||||
|
||||
this.props.appState.setTheme(themePath);
|
||||
shell.showItemInFolder(themePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
const canSubmit = !!this.state.file;
|
||||
|
||||
return [
|
||||
(
|
||||
<Button
|
||||
icon='add'
|
||||
key='submit'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Add'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public onClose() {
|
||||
this.props.appState.isThemeDialogShowing = false;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isThemeDialogShowing } = this.props.appState;
|
||||
const inputProps = { accept: '.json' };
|
||||
const { file } = this.state;
|
||||
|
||||
const text = file && file.path ? file.path : `Select the Monaco file...`;
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isThemeDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title='Add theme'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className='bp3-dialog-body'>
|
||||
<FileInput
|
||||
onInputChange={this.onChangeFile}
|
||||
inputProps={inputProps as any}
|
||||
text={text}
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this component's state
|
||||
*/
|
||||
private reset(): void {
|
||||
this.setState(this.resetState);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import { Button, Dialog, Label, MenuItem } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ElectronVersion } from '../../interfaces';
|
||||
import { Bisector } from '../bisect';
|
||||
import { AppState } from '../state';
|
||||
import { filterItem, getItemIcon, getVersionsFromAppState, renderItem } from './commands-version-chooser';
|
||||
|
||||
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
|
||||
|
||||
export interface BisectDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface BisectDialogState {
|
||||
startIndex?: number;
|
||||
endIndex?: number;
|
||||
allVersions: Array<ElectronVersion>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "add version" dialog allows users to add custom builds of Electron.
|
||||
*
|
||||
* @class AddVersionDialog
|
||||
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
|
||||
constructor(props: BisectDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onBeginSelect = this.onBeginSelect.bind(this);
|
||||
this.onEndSelect = this.onEndSelect.bind(this);
|
||||
|
||||
const allVersions = getVersionsFromAppState(this.props.appState);
|
||||
this.state = { allVersions };
|
||||
}
|
||||
|
||||
public onBeginSelect(version: ElectronVersion) {
|
||||
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
|
||||
}
|
||||
|
||||
public onEndSelect(version: ElectronVersion) {
|
||||
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of the dialog
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { endIndex, startIndex, allVersions } = this.state;
|
||||
const { appState } = this.props;
|
||||
|
||||
if (endIndex === undefined || startIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bisectRange = allVersions
|
||||
.slice(endIndex, startIndex + 1)
|
||||
.reverse();
|
||||
|
||||
appState.Bisector = new Bisector(bisectRange);
|
||||
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
|
||||
appState.setVersion(initialBisectPivot);
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the dialog
|
||||
*/
|
||||
public onClose() {
|
||||
this.props.appState.isBisectDialogShowing = false;
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
const canSubmit =
|
||||
!!this.state.startIndex &&
|
||||
!!this.state.endIndex &&
|
||||
this.state.startIndex > this.state.endIndex;
|
||||
|
||||
return [
|
||||
(
|
||||
<Button
|
||||
icon='play'
|
||||
key='submit'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Begin'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isBisectDialogShowing } = this.props.appState;
|
||||
const { startIndex, endIndex, allVersions } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isBisectDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title='Start a bisect session'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className='bp3-dialog-body'>
|
||||
<Label>
|
||||
Earliest Version
|
||||
<ElectronVersionSelect
|
||||
filterable={true}
|
||||
items={allVersions}
|
||||
itemRenderer={renderItem}
|
||||
itemPredicate={filterItem}
|
||||
onItemSelect={this.onBeginSelect}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
>
|
||||
<Button
|
||||
text={startIndex ? `v${allVersions[startIndex].version}` : ``}
|
||||
icon={startIndex ? getItemIcon(allVersions[startIndex]) : 'small-minus'}
|
||||
fill={true}
|
||||
/>
|
||||
</ElectronVersionSelect>
|
||||
</Label>
|
||||
<Label>
|
||||
Latest Version
|
||||
<ElectronVersionSelect
|
||||
filterable={true}
|
||||
items={allVersions.slice(0, startIndex!)}
|
||||
itemRenderer={renderItem}
|
||||
itemPredicate={filterItem}
|
||||
onItemSelect={this.onEndSelect}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
disabled={!startIndex}
|
||||
>
|
||||
<Button
|
||||
text={endIndex ? `v${allVersions[endIndex].version}` : ``}
|
||||
icon={endIndex ? getItemIcon(allVersions[endIndex]) : 'small-minus'}
|
||||
fill={true}
|
||||
disabled={!startIndex}
|
||||
/>
|
||||
</ElectronVersionSelect>
|
||||
</Label>
|
||||
</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ConfirmDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface ConfirmDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class ConfirmDialog
|
||||
* @extends {React.Component<ConfirmDialogProps, ConfirmDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class ConfirmDialog extends React.Component<ConfirmDialogProps, ConfirmDialogState> {
|
||||
constructor(props: ConfirmDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.confirmationPromptLastResult = result;
|
||||
this.props.appState.toggleConfirmationPromptDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isConfirmationPromptShowing, confirmationDialogTexts } = this.props.appState;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isConfirmationPromptShowing}
|
||||
onClose={this.onClose}
|
||||
icon='help'
|
||||
confirmButtonText={confirmationDialogTexts.ok}
|
||||
cancelButtonText={confirmationDialogTexts.cancel}
|
||||
intent={Intent.PRIMARY}
|
||||
>
|
||||
<p>{confirmationDialogTexts.label}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
|
||||
public async onSubmitToken(): Promise<void> {
|
||||
if (!this.state.tokenInput) return;
|
||||
this.setState({ verifying: true });
|
||||
this.props.appState.gitHubToken = this.state.tokenInput;
|
||||
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
@@ -64,16 +65,14 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
|
||||
this.props.appState.gitHubAvatarUrl = data.avatar_url;
|
||||
this.props.appState.gitHubLogin = data.login;
|
||||
this.props.appState.gitHubName = data.name;
|
||||
this.setState({ verifying: false, error: false });
|
||||
} catch (error) {
|
||||
console.warn(`Authenticating against GitHub failed`, error);
|
||||
this.setState({ verifying: false, error: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.appState.gitHubToken = this.state.tokenInput;
|
||||
this.props.appState.isTokenDialogShowing = false;
|
||||
|
||||
this.setState({ verifying: false, error: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,8 +13,7 @@ export interface WarningDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog asks the user for a GitHub Personal Access Token.
|
||||
* It's also responsible for checking if the token is correct.
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class WarningDialog
|
||||
@@ -30,12 +29,11 @@ export class WarningDialog extends React.Component<WarningDialogProps, WarningDi
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.warningDialogLastResult = result;
|
||||
this.props.appState.toogleWarningDialog();
|
||||
this.props.appState.toggleWarningDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isWarningDialogShowing}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { AddThemeDialog } from './dialog-add-theme';
|
||||
import { AddVersionDialog } from './dialog-add-version';
|
||||
import { BisectDialog } from './dialog-bisect';
|
||||
import { ConfirmDialog } from './dialog-confirm';
|
||||
import { TokenDialog } from './dialog-token';
|
||||
import { WarningDialog } from './dialog-warning';
|
||||
import { Settings } from './settings';
|
||||
@@ -21,7 +24,13 @@ export interface DialogsProps {
|
||||
export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing } = appState;
|
||||
const {
|
||||
isTokenDialogShowing,
|
||||
isSettingsShowing,
|
||||
isAddVersionDialogShowing,
|
||||
isThemeDialogShowing,
|
||||
isBisectDialogShowing
|
||||
} = appState;
|
||||
const maybeToken = isTokenDialogShowing
|
||||
? <TokenDialog key='dialogs' appState={appState} />
|
||||
: null;
|
||||
@@ -31,13 +40,23 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
const maybeAddLocalVersion = isAddVersionDialogShowing
|
||||
? <AddVersionDialog key='add-version-dialog' appState={appState} />
|
||||
: null;
|
||||
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
|
||||
: null;
|
||||
const maybeBisect = isBisectDialogShowing
|
||||
? <BisectDialog key='bisect-dialog' appState={appState} />
|
||||
: null;
|
||||
const eitherWarningOrPrompt = appState.isWarningDialogShowing
|
||||
? <WarningDialog appState={appState} />
|
||||
: <ConfirmDialog appState={appState} />;
|
||||
|
||||
return (
|
||||
<div key='dialogs' className='dialogs'>
|
||||
{maybeToken}
|
||||
{maybeSettings}
|
||||
{maybeAddLocalVersion}
|
||||
<WarningDialog appState={appState} />
|
||||
{maybeMonaco}
|
||||
{maybeBisect}
|
||||
{eitherWarningOrPrompt}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AppState } from '../state';
|
||||
export interface EditorProps {
|
||||
appState: AppState;
|
||||
monaco: typeof MonacoType;
|
||||
monoacoOptions: MonacoType.editor.IEditorOptions;
|
||||
monacoOptions: MonacoType.editor.IEditorOptions;
|
||||
id: EditorId;
|
||||
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
|
||||
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
|
||||
@@ -68,7 +68,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
* Initialize Monaco.
|
||||
*/
|
||||
public async initMonaco() {
|
||||
const { monaco, monoacoOptions } = this.props;
|
||||
const { monaco, monacoOptions: monacoOptions } = this.props;
|
||||
const ref = this.containerRef.current;
|
||||
|
||||
if (ref) {
|
||||
@@ -77,7 +77,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
theme: 'main',
|
||||
contextmenu: false,
|
||||
model: null,
|
||||
...monoacoOptions
|
||||
...monacoOptions
|
||||
});
|
||||
|
||||
await this.editorDidMount(this.editor);
|
||||
@@ -121,6 +121,9 @@ export class Editor extends React.Component<EditorProps> {
|
||||
} else {
|
||||
const value = await getContent(id, version);
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
model.updateOptions({
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
this.editor.setModel(model);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { MosaicWindowContext } from 'react-mosaic-component';
|
||||
import { MosaicContext, MosaicRootActions, MosaicWindowContext } from 'react-mosaic-component';
|
||||
|
||||
import { DocsDemoPage, MosaicId } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
@@ -11,86 +11,78 @@ export interface ToolbarButtonProps {
|
||||
id: MosaicId;
|
||||
}
|
||||
|
||||
export class MaximizeButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
export abstract class ToolbarButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextType = MosaicWindowContext;
|
||||
public context: MosaicWindowContext;
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
|
||||
this.expand = this.expand.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<MosaicContext.Consumer>
|
||||
{({ mosaicActions }) => this.createButton(mosaicActions)}
|
||||
</MosaicContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a button that performs the actual action
|
||||
*/
|
||||
public abstract createButton(_mosaicActions: MosaicRootActions<any>): React.ReactNode;
|
||||
}
|
||||
|
||||
export class MaximizeButton extends ToolbarButton {
|
||||
/**
|
||||
* Create a button that can expand this panel
|
||||
*/
|
||||
public createButton(mosaicActions: MosaicRootActions<any>) {
|
||||
const onClick = () => {
|
||||
mosaicActions.expand(this.context.mosaicWindowActions.getPath());
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon='maximize'
|
||||
className='bp3-small'
|
||||
onClick={this.expand}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this panel
|
||||
*/
|
||||
public expand() {
|
||||
const path = this.context.mosaicWindowActions.getPath();
|
||||
return this.context.mosaicActions.expand(path);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
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);
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
this.remove = this.remove.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Button
|
||||
icon='cross'
|
||||
className='bp3-small'
|
||||
onClick={this.remove}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this panel
|
||||
*/
|
||||
public remove() {
|
||||
this.props.appState.hideAndBackupMosaic(this.props.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class DocsDemoGoHomeButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
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;
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
this.goHome = this.goHome.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Button
|
||||
icon='home'
|
||||
className='bp3-small'
|
||||
onClick={this.goHome}
|
||||
onClick={onClick}
|
||||
text='Overview'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this panel
|
||||
*/
|
||||
public goHome() {
|
||||
this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as React from 'react';
|
||||
import { Mosaic, 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';
|
||||
@@ -27,14 +27,12 @@ const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
wordWrap: 'on'
|
||||
};
|
||||
|
||||
const ViewIdMosaic = Mosaic.ofType<EditorId>() as any;
|
||||
const ViewIdMosaicWindow = MosaicWindow.ofType<EditorId>() as any;
|
||||
|
||||
export const TITLE_MAP: Record<MosaicId, string> = {
|
||||
main: 'Main Process (main.js)',
|
||||
renderer: 'Renderer Process (renderer.js)',
|
||||
preload: 'Preload (preload.js)',
|
||||
html: 'HTML (index.html)',
|
||||
docsDemo: 'Docs & Demos'
|
||||
docsDemo: 'Docs & Demos',
|
||||
};
|
||||
|
||||
export interface EditorsProps {
|
||||
@@ -82,17 +80,11 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
ipcRendererManager.on(IpcEvents.FS_NEW_FIDDLE, async (_event) => {
|
||||
const { version } = this.props.appState;
|
||||
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: 'Your current fiddle is unsaved. Do you want to discard it?'
|
||||
});
|
||||
|
||||
window.ElectronFiddle.app.fileManager.setFiddle({
|
||||
values: {
|
||||
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) => {
|
||||
@@ -199,21 +191,21 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
* @param {string} path
|
||||
* @returns {JSX.Element | null}
|
||||
*/
|
||||
public renderTile(id: MosaicId, path: string): JSX.Element | null {
|
||||
public renderTile(id: MosaicId, path: Array<MosaicBranch>): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const content = isEditorId(id)
|
||||
? this.renderEditor(id)
|
||||
: this.renderGenericPanel(id, appState);
|
||||
|
||||
return (
|
||||
<ViewIdMosaicWindow
|
||||
<MosaicWindow<EditorId>
|
||||
className={id}
|
||||
path={path}
|
||||
title={TITLE_MAP[id]}
|
||||
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
|
||||
>
|
||||
{content}
|
||||
</ViewIdMosaicWindow>
|
||||
</MosaicWindow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -244,7 +236,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
id={id}
|
||||
monaco={monaco!}
|
||||
appState={appState}
|
||||
monoacoOptions={defaultMonacoOptions}
|
||||
monacoOptions={defaultMonacoOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -256,7 +248,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
if (!monaco) return null;
|
||||
|
||||
return (
|
||||
<ViewIdMosaic
|
||||
<Mosaic<EditorId | PanelId>
|
||||
onChange={this.onChange}
|
||||
value={appState.mosaicArrangement}
|
||||
zeroStateView={renderNonIdealState(appState)}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as React from 'react';
|
||||
import { AppState } from '../state';
|
||||
import { ChromeMac } from './chrome-mac';
|
||||
import { Commands } from './commands';
|
||||
import { Output } from './output';
|
||||
import { WelcomeTour } from './tour-welcome';
|
||||
|
||||
export interface HeaderProps {
|
||||
@@ -23,7 +22,6 @@ export class Header extends React.Component<HeaderProps, {}> {
|
||||
<ChromeMac appState={this.props.appState} />
|
||||
<header id='header'>
|
||||
<Commands key='commands' appState={this.props.appState} />
|
||||
<Output key='output' appState={this.props.appState} />
|
||||
</header>
|
||||
<WelcomeTour appState={this.props.appState} />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import * as React from 'react';
|
||||
import { Mosaic, MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { Editors } from './editors';
|
||||
import { Output } from './output';
|
||||
|
||||
export interface WrapperProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface WrapperState {
|
||||
mosaicArrangement: MosaicNode<WrapperMosaicId>;
|
||||
}
|
||||
|
||||
export type WrapperMosaicId = 'output' | 'editors';
|
||||
|
||||
export class OutputEditorsWrapper extends React.Component<WrapperProps, WrapperState> {
|
||||
|
||||
private MOSAIC_ELEMENTS = {
|
||||
output: <Output appState={this.props.appState} />,
|
||||
editors: <Editors appState={this.props.appState} />
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mosaicArrangement: {
|
||||
direction: 'column',
|
||||
first: 'output',
|
||||
second: 'editors',
|
||||
splitPercentage: 25,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Mosaic<WrapperMosaicId>
|
||||
renderTile={(id: string) => this.MOSAIC_ELEMENTS[id]}
|
||||
resize={{minimumPaneSizePercentage: 0}}
|
||||
value={this.state.mosaicArrangement}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onChange = (currentNode: any) => {
|
||||
const isConsoleShowing = currentNode.splitPercentage !== 0;
|
||||
|
||||
if (isConsoleShowing !== this.props.appState.isConsoleShowing) {
|
||||
this.props.appState.isConsoleShowing = isConsoleShowing;
|
||||
}
|
||||
|
||||
this.setState({mosaicArrangement: currentNode});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import { autorun } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
import { MosaicContext } from 'react-mosaic-component';
|
||||
|
||||
import { OutputEntry } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
import { WrapperMosaicId } from './output-editors-wrapper';
|
||||
|
||||
|
||||
export interface CommandsProps {
|
||||
appState: AppState;
|
||||
@@ -19,6 +23,8 @@ export interface CommandsProps {
|
||||
*/
|
||||
@observer
|
||||
export class Output extends React.Component<CommandsProps, {}> {
|
||||
public static contextType = MosaicContext;
|
||||
public context: MosaicContext<WrapperMosaicId>;
|
||||
private outputRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: CommandsProps) {
|
||||
@@ -28,6 +34,23 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
this.renderEntry = this.renderEntry.bind(this);
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount() {
|
||||
autorun(() => {
|
||||
const { isConsoleShowing } = this.props.appState;
|
||||
|
||||
// this context should always exist, but mocking context in enzyme
|
||||
// is not fully supported, so this condition makes the tests pass
|
||||
if (this.context.mosaicActions && this.context.mosaicActions.expand) {
|
||||
if (!isConsoleShowing) {
|
||||
this.context.mosaicActions.expand(['first'], 0);
|
||||
} else {
|
||||
this.context.mosaicActions.expand(['first'], 25);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.outputRef.current) {
|
||||
this.outputRef.current.scrollTop = this.outputRef.current.scrollHeight;
|
||||
@@ -71,9 +94,12 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
));
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isConsoleShowing, output } = this.props.appState;
|
||||
const className = isConsoleShowing ? 'output showing' : 'output';
|
||||
public render(): JSX.Element | null {
|
||||
const { output, isConsoleShowing } = this.props.appState;
|
||||
|
||||
if (!isConsoleShowing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The last 1000 lines
|
||||
const lines = output
|
||||
@@ -81,7 +107,7 @@ export class Output extends React.Component<CommandsProps, {}> {
|
||||
.map(this.renderEntry);
|
||||
|
||||
return (
|
||||
<div className={className} ref={this.outputRef}>
|
||||
<div className='output' ref={this.outputRef}>
|
||||
{lines}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Exection</h2>
|
||||
<h2>Execution</h2>
|
||||
<Callout>
|
||||
These advanced settings control how Electron Fiddle executes your fiddles.
|
||||
</Callout>
|
||||
|
||||
@@ -76,6 +76,7 @@ export class AppearanceSettings extends React.Component<
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.openThemeFolder = this.openThemeFolder.bind(this);
|
||||
this.handleAddTheme = this.handleAddTheme.bind(this);
|
||||
|
||||
this.state = {
|
||||
themes: []
|
||||
@@ -111,8 +112,9 @@ export class AppearanceSettings extends React.Component<
|
||||
* @memberof AppearanceSettings
|
||||
*/
|
||||
public async createNewThemeFromCurrent(): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const theme = await getTheme(this.props.appState.theme);
|
||||
const theme = await getTheme(appState.theme);
|
||||
|
||||
try {
|
||||
const namor = await fancyImport<any>('namor');
|
||||
@@ -128,6 +130,8 @@ export class AppearanceSettings extends React.Component<
|
||||
|
||||
shell.showItemInFolder(themePath);
|
||||
|
||||
this.setState({themes: await getAvailableThemes()});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn(`Themes: Failed to create new theme from current`, error);
|
||||
@@ -190,13 +194,29 @@ export class AppearanceSettings extends React.Component<
|
||||
</a>. The easiest way to get started is to clone one of the two existing
|
||||
themes and to add your own colors.
|
||||
</p>
|
||||
<p>
|
||||
Additionally, if you wish to import a Monaco Editor theme, pick your JSON file and Fiddle will attempt to import it.
|
||||
</p>
|
||||
<Button
|
||||
onClick={this.createNewThemeFromCurrent}
|
||||
text='Create theme from current selection'
|
||||
icon='duplicate'
|
||||
/>
|
||||
<Button
|
||||
icon='document-open'
|
||||
onClick={this.handleAddTheme}
|
||||
text='Add a Monaco Editor theme'
|
||||
/>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the "add monaco theme" dialog
|
||||
*/
|
||||
public handleAddTheme(): void {
|
||||
this.props.appState.toggleAddMonacoThemeDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Callout, Checkbox, FormGroup } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ConsoleSettingsProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings content to manage console-related preferences.
|
||||
*
|
||||
* @class ConsoleSettings
|
||||
* @extends {React.Component<ConsoleSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class ConsoleSettings extends React.Component<ConsoleSettingsProps, {}> {
|
||||
constructor(props: ConsoleSettingsProps) {
|
||||
super(props);
|
||||
|
||||
this.handleClearOnRunChange = this.handleClearOnRunChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change on whether or not the console should be cleared
|
||||
* before fiddle is executed.
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleClearOnRunChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { checked } = event.currentTarget;
|
||||
this.props.appState.isClearingConsoleOnRun = checked;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isClearingConsoleOnRun } = this.props.appState;
|
||||
|
||||
const clearOnRunLabel = `
|
||||
Enable this option to automatically clear the console whenever you run your
|
||||
fiddle.`.trim();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Console</h4>
|
||||
<Callout>
|
||||
<FormGroup label={clearOnRunLabel}>
|
||||
<Checkbox
|
||||
checked={isClearingConsoleOnRun}
|
||||
label='Clear on run.'
|
||||
onChange={this.handleClearOnRunChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class GitHubSettings extends React.Component<GitHubSettingsProps, {}> {
|
||||
return (
|
||||
<Callout>
|
||||
<p>
|
||||
Your fiddles can be published as public GitHub Gists -
|
||||
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'/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { AppearanceSettings } from './settings-general-appearance';
|
||||
import { ConsoleSettings } from './settings-general-console';
|
||||
import { GitHubSettings } from './settings-general-github';
|
||||
|
||||
export interface GeneralSettingsProps {
|
||||
@@ -24,6 +25,8 @@ export class GeneralSettings extends React.Component<GeneralSettingsProps, {}> {
|
||||
<h2>General Settings</h2>
|
||||
<AppearanceSettings appState={this.props.appState} />
|
||||
<Divider />
|
||||
<ConsoleSettings appState={this.props.appState} />
|
||||
<Divider />
|
||||
<GitHubSettings appState={this.props.appState} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -44,6 +44,16 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
this.state = {
|
||||
section: SettingsSections.General
|
||||
};
|
||||
|
||||
this.closeSettingsPanel = this.closeSettingsPanel.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
window.addEventListener('keyup', this.closeSettingsPanel, true);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.closeSettingsPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,4 +148,14 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
|
||||
return 'cog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger closing of the settings panel upon Esc
|
||||
*/
|
||||
private closeSettingsPanel(event: KeyboardEvent) {
|
||||
const { appState } = this.props;
|
||||
if (event.code === 'Escape') {
|
||||
appState.isSettingsShowing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ShowMeApp(_props: any): JSX.Element {
|
||||
|
||||
let result = '';
|
||||
pathsToQuery.forEach((item) => {
|
||||
result += `${item}: ${remote.app.getPath(item)}\n`;
|
||||
result += `${item}: ${remote.app.getPath(item as any)}\n`;
|
||||
});
|
||||
|
||||
setPaths(result);
|
||||
|
||||
@@ -115,7 +115,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
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>.
|
||||
<code>BrowserWindow</code>.
|
||||
</p>
|
||||
<p>
|
||||
To get started, pretend that the main process is just like a Node.js
|
||||
@@ -137,10 +137,10 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<p>
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
|
||||
<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>
|
||||
<code><script /></code> tag and notice how we can call <code>
|
||||
require()</code> like we would in Node.js.
|
||||
</p>
|
||||
)
|
||||
@@ -157,7 +157,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
</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
|
||||
<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>
|
||||
</>
|
||||
|
||||
@@ -18,3 +18,6 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
second: EditorId.html,
|
||||
}
|
||||
};
|
||||
|
||||
export const ELECTRON_ORG = 'electron';
|
||||
export const ELECTRON_REPO = 'electron';
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function getContent(
|
||||
|
||||
if (name === EditorId.main) {
|
||||
// We currently only distinguish between loadFile
|
||||
// and loadURL. Todo: Properly version the quick-start.
|
||||
// and loadURL. TODO: Properly version the quick-start.
|
||||
if (version && version.startsWith('1.')) {
|
||||
return (await import('../content/main-1-x-x')).main;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ 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.getValues({ 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) {
|
||||
|
||||
+58
-21
@@ -2,28 +2,38 @@ 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');
|
||||
|
||||
|
||||
/**
|
||||
* Fetch TypeScript definitions for the current version of Electron (online)
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function fetchTypeDefinitions(version: string): Promise<string> {
|
||||
export async function fetchTypeDefinitions(version: string): Promise<string> {
|
||||
const url = `https://unpkg.com/electron@${version}/electron.d.ts`;
|
||||
|
||||
return window.fetch(url)
|
||||
.then((response) => response.text())
|
||||
.catch((error) => {
|
||||
console.warn(`Fetch Types: Could not fetch definitions`, error);
|
||||
return '';
|
||||
});
|
||||
let text: string;
|
||||
try {
|
||||
const response = await window.fetch(url);
|
||||
text = await response.text();
|
||||
} catch (error) {
|
||||
console.warn(`Fetch Types: Could not fetch definitions`, error);
|
||||
return '';
|
||||
}
|
||||
|
||||
// for invalid packa
|
||||
if (text.includes('Cannot find package')) {
|
||||
console.warn(`Fetch Types: ${text}`);
|
||||
return '';
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,27 +58,25 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TypeScript defintions for a version of Electron. If none can't be
|
||||
* Get TypeScript definitions for a version of Electron. If none can be
|
||||
* found, returns null.
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function getTypeDefinitions(version: string): Promise<string | null> {
|
||||
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
await fs.mkdirp(definitionPath);
|
||||
await fs.mkdirp(definitionPath);
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version.version);
|
||||
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version);
|
||||
|
||||
if (await getOfflineTypeDefinitions(version)) {
|
||||
if (await getOfflineTypeDefinitions(version.version)) {
|
||||
try {
|
||||
return await fs.readFile(offlinePath, 'utf-8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
const typeDefs = await fetchTypeDefinitions(version);
|
||||
|
||||
const typeDefs = await fetchTypeDefinitions(version.version);
|
||||
if (typeDefs && typeDefs.length > 0) {
|
||||
try {
|
||||
await fs.outputFile(offlinePath, typeDefs);
|
||||
@@ -79,17 +87,27 @@ export async function getTypeDefinitions(version: string): Promise<string | null
|
||||
return typeDefs;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to update the editor with type definitions.
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
export async function updateEditorTypeDefinitions(version: string, i: number = 0): Promise<void> {
|
||||
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
|
||||
const defer = async (): Promise<void> => {
|
||||
if (i > 10) {
|
||||
console.warn(`Fetch Types: Failed, dependencies do not exist`);
|
||||
@@ -106,17 +124,36 @@ export async function updateEditorTypeDefinitions(version: string, i: number = 0
|
||||
const { app } = window.ElectronFiddle;
|
||||
const monaco: typeof MonacoType = app.monaco!;
|
||||
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
|
||||
const typeDefs = await getTypeDefinitions(version);
|
||||
|
||||
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
|
||||
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
|
||||
|
||||
const typeDefs = await getTypeDefs(version);
|
||||
|
||||
if (typeDefDisposable) {
|
||||
typeDefDisposable.dispose();
|
||||
}
|
||||
|
||||
if (typeDefs) {
|
||||
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version}`);
|
||||
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} 😢`);
|
||||
console.log(`Fetch Types: No type definitions for ${version.version} 😢`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalTypePathForVersion(version: ElectronVersion) {
|
||||
if (version.localPath) {
|
||||
return path.join(
|
||||
version.localPath,
|
||||
'gen',
|
||||
'electron',
|
||||
'tsc',
|
||||
'typings',
|
||||
'electron.d.ts'
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { EditorValues, Files, FileTransform, SetFiddleOptions } from '../interfaces';
|
||||
import { EditorValues, Files, FileTransform } from '../interfaces';
|
||||
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, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
|
||||
import { getTitle } from '../utils/get-title';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { AppState } from './state';
|
||||
@@ -38,13 +37,12 @@ export class FileManager {
|
||||
/**
|
||||
* Opens a template.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} templateName
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async openTemplate(name: string) {
|
||||
const values = await getTemplateValues(name);
|
||||
|
||||
this.setFiddle({ values, templateName: name });
|
||||
public async openTemplate(templateName: string) {
|
||||
const editorValues = await getTemplateValues(templateName);
|
||||
await window.ElectronFiddle.app.replaceFiddle(editorValues, {templateName});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,34 +58,15 @@ export class FileManager {
|
||||
|
||||
// We'll do our best and will likely have to
|
||||
// rewrite this once we support multiple files
|
||||
const values: EditorValues = {
|
||||
const editorValues: EditorValues = {
|
||||
html: await this.readFile(path.join(filePath, INDEX_HTML_NAME)),
|
||||
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)),
|
||||
};
|
||||
|
||||
this.setFiddle({ values, filePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Got values? This method will load them.
|
||||
*
|
||||
* @param {SetFiddleOptions} options
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async setFiddle({ values, filePath, templateName }: SetFiddleOptions) {
|
||||
this.appState.gistId = '';
|
||||
this.appState.isMyGist = false;
|
||||
this.appState.localPath = filePath;
|
||||
this.appState.templateName = templateName;
|
||||
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Opening this fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues(values);
|
||||
document.title = getTitle(this.appState);
|
||||
window.ElectronFiddle.app.replaceFiddle(editorValues, {filePath});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +88,15 @@ export class FileManager {
|
||||
const files = await this.getFiles(undefined, ...transforms);
|
||||
|
||||
for (const [fileName, content] of files) {
|
||||
await this.saveFile(path.join(pathToSave, fileName), content);
|
||||
const savePath = path.join(pathToSave, fileName);
|
||||
|
||||
// If the file has content, save it to disk. If there's no
|
||||
// content in the file, remove a file that possibly exists.
|
||||
if (content) {
|
||||
await this.saveFile(savePath, content);
|
||||
} else {
|
||||
await this.removeFile(savePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (pathToSave !== localPath) {
|
||||
@@ -132,12 +119,13 @@ export class FileManager {
|
||||
*/
|
||||
public async getFiles(options?: PackageJsonOptions, ...transforms: Array<FileTransform>): Promise<Files> {
|
||||
const pOptions = typeof options === 'object' ? options : DEFAULT_OPTIONS;
|
||||
const values = await window.ElectronFiddle.app.getValues(pOptions);
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(pOptions);
|
||||
let output: Files = new Map();
|
||||
|
||||
output.set(RENDERER_JS_NAME, values.renderer);
|
||||
output.set(MAIN_JS_NAME, values.main);
|
||||
output.set(INDEX_HTML_NAME, values.html);
|
||||
output.set(PRELOAD_JS_NAME, values.preload);
|
||||
output.set(PACKAGE_NAME, values.package!);
|
||||
|
||||
for (const transform of transforms) {
|
||||
@@ -240,4 +228,22 @@ export class FileManager {
|
||||
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely attempts to remove a file, doesn't crash the app if
|
||||
* it fails.
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @returns {string}
|
||||
* @memberof 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);
|
||||
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -16,7 +16,7 @@ const ignoredModules: Array<string> = [
|
||||
];
|
||||
|
||||
/* regular expression to both match and extract module names */
|
||||
const requiregx = /require\(['"](.*?)['"]\)/gm;
|
||||
const requiregx = /^.*require\(['"](.*?)['"]\)/gm;
|
||||
|
||||
|
||||
/*
|
||||
@@ -91,8 +91,11 @@ export function findModules(input: string): Array<string> {
|
||||
/* grab all global require matches in the text */
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
while (match = (requiregx.exec(input) || null)) {
|
||||
const mod = match[1];
|
||||
modules.push(mod);
|
||||
// ensure commented-out requires aren't downloaded
|
||||
if (!match[0].startsWith('//')) {
|
||||
const mod = match[1];
|
||||
modules.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
/* map and reduce */
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
import * as Octokit from '@octokit/rest';
|
||||
import { when } from 'mobx';
|
||||
import { EditorId, EditorValues } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { getOctokit } from '../utils/octokit';
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
|
||||
import { getContent } from './content';
|
||||
import { AppState } from './state';
|
||||
import { ElectronReleaseChannel, getReleaseChannel } from './versions';
|
||||
|
||||
export class RemoteLoader {
|
||||
constructor(private readonly appState: AppState) {
|
||||
this.loadFiddleFromElectronExample.bind(this);
|
||||
this.loadFiddleFromGist.bind(this);
|
||||
this.verifyRemoteLoad.bind(this);
|
||||
this.verifyReleaseChannelEnabled.bind(this);
|
||||
this.fetchExampleAndLoad.bind(this);
|
||||
this.fetchGistAndLoad.bind(this);
|
||||
this.setElectronVersionWithRef.bind(this);
|
||||
this.getPackageVersionFromRef.bind(this);
|
||||
this.handleLoadingSuccess.bind(this);
|
||||
this.handleLoadingFailed.bind(this);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs', exampleInfo.ref);
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.appState);
|
||||
|
||||
const folder = await octo.repos.getContents({
|
||||
owner: ELECTRON_REPO,
|
||||
repo: ELECTRON_ORG,
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
|
||||
const ok = await this.setElectronVersionWithRef(ref);
|
||||
if (!ok) return false;
|
||||
|
||||
const values = {
|
||||
html: await getContent(EditorId.html, this.appState.version),
|
||||
renderer: await getContent(EditorId.renderer, this.appState.version),
|
||||
main: await getContent(EditorId.main, this.appState.version),
|
||||
preload: await getContent(EditorId.preload, 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');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
if (!child.download_url) {
|
||||
console.warn(`Could not find download_url for ${child.name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case PRELOAD_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.preload = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(loaders);
|
||||
|
||||
return this.handleLoadingSuccess(values, '');
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a gist. If it doesn't exist, return an empty string.
|
||||
*
|
||||
* @param {Octokit.Response<Octokit.GistsGetResponse>} gist
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
* @memberof RemoteLoader
|
||||
*/
|
||||
public getContentOrEmpty(gist: Octokit.Response<Octokit.GistsGetResponse>, name: string): string {
|
||||
try {
|
||||
return gist.data.files[name].content;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fiddle
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof RemoteLoader
|
||||
*/
|
||||
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
|
||||
try {
|
||||
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)
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async setElectronVersionWithRef(ref: string): Promise<boolean> {
|
||||
const version = await this.getPackageVersionFromRef(ref);
|
||||
|
||||
const supportedVersions = sortedElectronMap(this.appState.versions, (k) => k);
|
||||
if (!supportedVersions.includes(version)) {
|
||||
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);
|
||||
|
||||
if (!this.appState.versionsToShow.includes(versionReleaseChannel)) {
|
||||
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
|
||||
if (!ok) return false;
|
||||
|
||||
this.appState.versionsToShow.push(versionReleaseChannel);
|
||||
}
|
||||
|
||||
this.appState.setVersion(version);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getPackageVersionFromRef(ref: string): Promise<string> {
|
||||
const octo = await getOctokit(this.appState);
|
||||
const { data: packageJsonData } = await octo.repos.getContents({
|
||||
owner: ELECTRON_ORG,
|
||||
repo: ELECTRON_REPO,
|
||||
ref,
|
||||
path: 'package.json'
|
||||
});
|
||||
|
||||
if (!Array.isArray(packageJsonData) && !!packageJsonData.content) {
|
||||
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
|
||||
});
|
||||
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies from the user that we should be loading this fiddle
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string, fiddlePath?: string): Promise<boolean> {
|
||||
this.appState.setConfirmationPromptTexts({
|
||||
label: `Are you sure you sure you want to load this '${what}' from fiddle path '${fiddlePath}'? Only load and run it if you trust the source.`
|
||||
});
|
||||
this.appState.isConfirmationPromptShowing = true;
|
||||
await when(() => !this.appState.isConfirmationPromptShowing);
|
||||
|
||||
return !!this.appState.confirmationPromptLastResult;
|
||||
}
|
||||
|
||||
public async verifyReleaseChannelEnabled(channel: string): Promise<boolean> {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `You're loading an example with a version of Electron with an unincluded release
|
||||
channel (${channel}). Do you want to enable the release channel to load the
|
||||
version of Electron from the example?`
|
||||
});
|
||||
this.appState.isWarningDialogShowing = true;
|
||||
await when(() => !this.appState.isWarningDialogShowing);
|
||||
|
||||
return !!this.appState.warningDialogLastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub succeeded, let's move on.
|
||||
*
|
||||
* @param {Partial<EditorValues>} values
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
await window.ElectronFiddle.app.replaceFiddle(values, {gistId});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub failed - this method handles this case
|
||||
* gracefully.
|
||||
*
|
||||
* @param {Error} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
if (navigator.onLine) {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
} else {
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
}
|
||||
|
||||
this.appState.toggleWarningDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,17 @@ export class Runner {
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async run(): Promise<boolean> {
|
||||
const { fileManager, getValues } = window.ElectronFiddle.app;
|
||||
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
|
||||
const options = { includeDependencies: false, includeElectron: false };
|
||||
const { binaryManager, currentElectronVersion } = this.appState;
|
||||
const { version, localPath } = currentElectronVersion;
|
||||
|
||||
if (this.appState.isClearingConsoleOnRun) {
|
||||
this.appState.clearConsole();
|
||||
}
|
||||
this.appState.isConsoleShowing = true;
|
||||
|
||||
const values = await getValues(options);
|
||||
const values = await getEditorValues(options);
|
||||
const dir = await this.saveToTemp(options);
|
||||
|
||||
if (!dir) return false;
|
||||
@@ -197,8 +200,8 @@ export class Runner {
|
||||
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()}.`
|
||||
|
||||
+110
-38
@@ -1,3 +1,4 @@
|
||||
import * as fsType from 'fs-extra';
|
||||
import { action, autorun, computed, observable, when } from 'mobx';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
@@ -19,14 +20,17 @@ 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 { updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { activateTheme } from './themes';
|
||||
|
||||
import {
|
||||
addLocalVersion,
|
||||
ElectronReleaseChannel,
|
||||
@@ -70,36 +74,45 @@ 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 isMyGist: 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 isRunning = false;
|
||||
@observable public confirmationPromptLastResult: boolean | null = null;
|
||||
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
|
||||
@observable public templateName: string | undefined;
|
||||
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
@observable public localTypeWatcher: fsType.FSWatcher | undefined;
|
||||
@observable public 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');
|
||||
|
||||
// -- Editor Values stored when we close the editor ------------------
|
||||
@@ -119,16 +132,22 @@ 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));
|
||||
autorun(() => this.save('isClearingConsoleOnRun', this.isClearingConsoleOnRun));
|
||||
autorun(() => this.save('gitHubAvatarUrl', this.gitHubAvatarUrl));
|
||||
autorun(() => this.save('gitHubLogin', this.gitHubLogin));
|
||||
autorun(() => this.save('gitHubName', this.gitHubName));
|
||||
@@ -226,15 +245,30 @@ export class AppState {
|
||||
this.isConsoleShowing = !this.isConsoleShowing;
|
||||
}
|
||||
|
||||
@action public clearConsole() {
|
||||
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;
|
||||
}
|
||||
|
||||
@action public toggleAddMonacoThemeDialog() {
|
||||
this.isThemeDialogShowing = !this.isThemeDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleAuthDialog() {
|
||||
this.isTokenDialogShowing = !this.isTokenDialogShowing;
|
||||
}
|
||||
|
||||
@action public toogleWarningDialog() {
|
||||
@action public toggleWarningDialog() {
|
||||
this.isWarningDialogShowing = !this.isWarningDialogShowing;
|
||||
|
||||
if (this.isWarningDialogShowing) {
|
||||
@@ -242,6 +276,18 @@ export class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleBisectDialog() {
|
||||
this.isBisectDialogShowing = !this.isBisectDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleConfirmationPromptDialog() {
|
||||
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
|
||||
|
||||
if (this.isConfirmationPromptShowing) {
|
||||
this.confirmationPromptLastResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleSettings() {
|
||||
// We usually don't lose editor focus,
|
||||
// so you can still type. Let's force-blur.
|
||||
@@ -275,6 +321,14 @@ export class AppState {
|
||||
};
|
||||
}
|
||||
|
||||
@action public setConfirmationPromptTexts(input: WarningDialogTexts) {
|
||||
this.confirmationDialogTexts = {
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
@action public addLocalVersion(input: NpmVersion) {
|
||||
addLocalVersion(input);
|
||||
|
||||
@@ -282,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];
|
||||
@@ -321,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}`);
|
||||
@@ -350,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);
|
||||
|
||||
@@ -373,21 +427,38 @@ export class AppState {
|
||||
// Should we update the editor?
|
||||
if (await isContentUnchanged(EditorId.main)) {
|
||||
const main = await getContent(EditorId.main, version);
|
||||
window.ElectronFiddle.app.setValues({ main }, false);
|
||||
window.ElectronFiddle.app.setEditorValues({ main });
|
||||
}
|
||||
|
||||
// Update TypeScript definitions
|
||||
updateEditorTypeDefinitions(version);
|
||||
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 () => {
|
||||
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
|
||||
await updateEditorTypeDefinitions(versionObject);
|
||||
});
|
||||
} else {
|
||||
if (!!this.localTypeWatcher) {
|
||||
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
|
||||
this.localTypeWatcher.close();
|
||||
this.localTypeWatcher = undefined;
|
||||
}
|
||||
}
|
||||
await updateEditorTypeDefinitions(versionObject);
|
||||
|
||||
// Fetch new binaries, maybe?
|
||||
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 };
|
||||
@@ -426,7 +497,7 @@ export class AppState {
|
||||
let strData = data.toString();
|
||||
const { isNotPre, bypassBuffer } = options;
|
||||
|
||||
// Todo: This drops the first part of the buffer... is that fully expected?
|
||||
// TODO: This drops the first part of the buffer... is that fully expected?
|
||||
if (process.platform === 'win32' && bypassBuffer === false) {
|
||||
this.outputBuffer += strData;
|
||||
strData = this.outputBuffer;
|
||||
@@ -524,7 +595,7 @@ export class AppState {
|
||||
*/
|
||||
@action public showMosaic(id: MosaicId) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
this.setVisibleMosaics([ ...currentlyVisible, id ]);
|
||||
this.setVisibleMosaics([...currentlyVisible, id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,6 +610,7 @@ export class AppState {
|
||||
this.isTourShowing = false;
|
||||
this.isConsoleShowing = false;
|
||||
this.isAddVersionDialogShowing = false;
|
||||
this.isThemeDialogShowing = false;
|
||||
|
||||
if (additionalOptions) {
|
||||
for (const key in additionalOptions) {
|
||||
|
||||
@@ -2,12 +2,11 @@ import * as fsExtraType from 'fs-extra';
|
||||
import * as pathType from 'path';
|
||||
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { fancyImport } from '../utils/import';
|
||||
|
||||
/**
|
||||
* Returns expected content for a given name. Currently synchronous,
|
||||
* but probably shouldn't be.
|
||||
* Returns expected content for a given name.
|
||||
*
|
||||
* @param {string} name
|
||||
* @returns {Promise<EditorValues>}
|
||||
@@ -41,6 +40,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
return {
|
||||
renderer: await getFile(RENDERER_JS_NAME),
|
||||
main: await getFile(MAIN_JS_NAME),
|
||||
html: await getFile(INDEX_HTML_NAME)
|
||||
html: await getFile(INDEX_HTML_NAME),
|
||||
preload: await getFile(PRELOAD_JS_NAME)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,13 +45,14 @@ export function getDefaultVersion(
|
||||
* Return the release channel for a given input
|
||||
* version.
|
||||
*
|
||||
* @param {NpmVersion} input
|
||||
* @param {NpmVersion | string} input
|
||||
* @returns {ElectronReleaseChannel}
|
||||
*/
|
||||
export function getReleaseChannel(
|
||||
input: NpmVersion
|
||||
input: NpmVersion | string
|
||||
): ElectronReleaseChannel {
|
||||
const tag = input.version || '';
|
||||
|
||||
const tag = (typeof input === 'string') ? input : (input.version || '');
|
||||
|
||||
if (tag.includes('beta')) {
|
||||
return ElectronReleaseChannel.beta;
|
||||
@@ -235,13 +236,18 @@ export async function getUpdatedElectronVersions(
|
||||
* @returns {Promise<Array<NpmVersion>>}
|
||||
*/
|
||||
export async function fetchVersions() {
|
||||
const response = await window.fetch(`https://registry.npmjs.org/electron`);
|
||||
const data = await response.json();
|
||||
const versions: Record<string, any> = data.versions;
|
||||
const channels = [
|
||||
`https://registry.npmjs.org/electron`, // stable, beta
|
||||
`https://registry.npmjs.org/electron-nightly` // nightly
|
||||
];
|
||||
|
||||
const output: Array<NpmVersion> = Object
|
||||
.keys(versions)
|
||||
.map((version) => ({ version }));
|
||||
const output: Array<NpmVersion> = [];
|
||||
for (const channelUrl of channels) {
|
||||
const response = await window.fetch(channelUrl);
|
||||
const data = await response.json();
|
||||
const versions: Record<string, any> = data.versions;
|
||||
output.push(...Object.keys(versions).map((version) => ({ version })));
|
||||
}
|
||||
|
||||
if (output && output.length > 0 && isExpectedFormat(output)) {
|
||||
console.log(`Fetched new Electron versions (Count: ${output.length})`);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const INDEX_HTML_NAME = 'index.html';
|
||||
export const MAIN_JS_NAME = 'main.js';
|
||||
export const RENDERER_JS_NAME = 'renderer.js';
|
||||
export const PRELOAD_JS_NAME = 'preload.js';
|
||||
export const PACKAGE_NAME = 'package.json';
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* a debounced version below.
|
||||
*/
|
||||
function _updateEditorLayout() {
|
||||
const { main, renderer, html } = window.ElectronFiddle.editors;
|
||||
|
||||
if (main) main.layout();
|
||||
if (renderer) renderer.layout();
|
||||
if (html) html.layout();
|
||||
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
|
||||
if (window.ElectronFiddle.editors[key]) {
|
||||
window.ElectronFiddle.editors[key].layout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let handle: number;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { AboutPanelOptionsOptions, app } from 'electron';
|
||||
* @returns
|
||||
*/
|
||||
export function setupAboutPanel(): void {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const options: AboutPanelOptionsOptions = {
|
||||
applicationName: 'Electron Fiddle',
|
||||
applicationVersion: app.getVersion(),
|
||||
|
||||
@@ -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>
|
||||
@@ -14,7 +14,7 @@ app.on('ready', () => {
|
||||
win = null
|
||||
})
|
||||
|
||||
let view = new BrowserView({
|
||||
const view = new BrowserView({
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello from your BrowserWindow!</h1>
|
||||
<button></button>
|
||||
<button id="close-btn">Close this window</button>
|
||||
<script>
|
||||
document.querySelector('Close this window').onclick = () => {
|
||||
window.close()
|
||||
}
|
||||
const button = document.querySelector('#close-btn')
|
||||
button.addEventListener('click', () => window.close())
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// the result.
|
||||
//
|
||||
// For more info, see:
|
||||
// https://electronjs.org/docs/api/contentTracing
|
||||
// https://electronjs.org/docs/api/content-tracing
|
||||
|
||||
const { app, contentTracing } = require('electron')
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@ app.on('ready', () => {
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
mainWindow.webContents.on('crashed', () => {
|
||||
console.log(`Window crashed!`)
|
||||
console.log('Window crashed!')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -22,13 +22,13 @@ app.on('ready', () => {
|
||||
// CommandOrControl which represents Command on macOS and Control on
|
||||
// Linux and Windows to define some accelerators.
|
||||
globalShortcut.register('CommandOrControl+Y', () => {
|
||||
console.log(`The global shortkey was pressed!`)
|
||||
console.log('The global shortkey was pressed!')
|
||||
})
|
||||
|
||||
// It supports "special names". Check out the API documentation for a full
|
||||
// list.
|
||||
globalShortcut.register('VolumeUp', () => console.log(`Turn it up!`))
|
||||
globalShortcut.register('VolumeDown', () => console.log(`Turn it down!`))
|
||||
globalShortcut.register('VolumeUp', () => console.log('Turn it up!'))
|
||||
globalShortcut.register('VolumeDown', () => console.log('Turn it down!'))
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
|
||||
@@ -20,21 +20,21 @@ app.on('ready', () => {
|
||||
if (Notification.isSupported()) {
|
||||
const notification = new Notification({
|
||||
title: 'Hello World!',
|
||||
subtitle: `Nice to see you`,
|
||||
subtitle: 'Nice to see you',
|
||||
body: 'Are you having a good day?',
|
||||
hasReply: true
|
||||
})
|
||||
|
||||
notification.on('show', () => console.log(`Notification shown`))
|
||||
notification.on('click', () => console.log(`Notification clicked`))
|
||||
notification.on('close', () => console.log(`Notification closed`))
|
||||
notification.on('show', () => console.log('Notification shown'))
|
||||
notification.on('click', () => console.log('Notification clicked'))
|
||||
notification.on('close', () => console.log('Notification closed'))
|
||||
notification.on('reply', (event, reply) => {
|
||||
console.log(`Reply: ${reply}`)
|
||||
})
|
||||
|
||||
notification.show()
|
||||
} else {
|
||||
console.log(`Hm, are notifications supported on this system?`)
|
||||
console.log('Hm, are notifications supported on this system?')
|
||||
}
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
@@ -19,10 +19,10 @@ app.on('ready', () => {
|
||||
})
|
||||
|
||||
powerMonitor.on('on-ac', () => {
|
||||
console.log(`We're on AC power`)
|
||||
console.log('We\'re on AC power')
|
||||
})
|
||||
|
||||
powerMonitor.on('on-battery', () => {
|
||||
console.log(`We're on AC power`)
|
||||
console.log('We\'re on battery power')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -87,17 +87,19 @@ app.on('ready', () => {
|
||||
spinning = false
|
||||
}
|
||||
|
||||
const touchBar = new TouchBar([
|
||||
spin,
|
||||
new TouchBarSpacer({ size: 'large' }),
|
||||
reel1,
|
||||
new TouchBarSpacer({ size: 'small' }),
|
||||
reel2,
|
||||
new TouchBarSpacer({ size: 'small' }),
|
||||
reel3,
|
||||
new TouchBarSpacer({ size: 'large' }),
|
||||
result
|
||||
])
|
||||
const touchBar = new TouchBar({
|
||||
items: [
|
||||
spin,
|
||||
new TouchBarSpacer({ size: 'large' }),
|
||||
reel1,
|
||||
new TouchBarSpacer({ size: 'small' }),
|
||||
reel2,
|
||||
new TouchBarSpacer({ size: 'small' }),
|
||||
reel3,
|
||||
new TouchBarSpacer({ size: 'large' }),
|
||||
result
|
||||
]
|
||||
})
|
||||
|
||||
mainWindow.setTouchBar(touchBar)
|
||||
})
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
const { app, Tray, Menu, nativeImage } = require('electron')
|
||||
|
||||
let tray
|
||||
|
||||
app.on('ready', () => {
|
||||
const icon = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAACsZJREFUWAmtWFlsXFcZ/u82++Jt7IyT2Em6ZFHTpAtWIzspEgjEUhA8VNAiIYEQUvuABBIUwUMkQIVKPCIoEiABLShISEBbhFJwIGRpIKRpbNeJ7bh2HHvssR3PPnPnLnzfmRlju6EQqUc+c++c8y/fv54z1uQOh+/7Glh0TD59TE/TND7lnfa4/64OKsM071QoeZpA/y9WWvk/B4XCC06TUC+Xyw8HTXNQ1+Ww6PpOrMebewXxvBueJ6/XHOdMJBL5J9Y97m2R0SS/wweE6JxkGx5dilWr1S/7dXsEa2o4+LyFmcFcaL5zbX3Y9gh5hpeWYpSB9XV5/H678V89BGYDXnHJlCsWn4gHrGc1K9CXxferOdvPOOKUfF8cH7nUyCtklQZXih/VNNlmirk3GdBSoIcRswW7/vVkLPYi5W2Uze8bh7J+4wLfh4dViFx5/nmrUi7/MhGNvrCkBfpeWqnW/7BUdadqntQ8zwr6vhUV34xpYnDynWvcmwQNaclDXsqgLMqkocPDw7fNx7d5qIX+/PmJxKGD6VdDkeh7ztyqOFfrokGCEWiiZ1mp0uITnuKAosaT7+pNxMYTyefutcQfbA+b1XLpH5fnF97/yD335Fu6mqTqsclDINBVmI4fDxw80KPAvJSt1MZtMcLiGxYUu83p4UkgnJZlqcl3LAj3WnTkIS9lUBYNPJjueVWgg7qocyOgliFqjZsg8gq5tRdiieQTf1gq15Y8CUbRZtyWOzZwc8lEqS3PTCtgqd13ieO68BQ2uNl64tXAewktrFuX2mPdkWAxn3sxnmx7sqUTJGqso8MGS9tbXFz8DMH8bblUX3T9QARVi8RV8qljfcJy0zRlaf6mzHEuzEtmekqCoZB4rqp0OmudHtUnlEWZlE0d1EWd1N3EozourcO65pw4eTIZQTW9VazJtbqvw9XwKVFQMsKDBuNhtp4uvGGFI+IDgKnpMjYyIis3ZsQMBIR7pONsIaMsyqRs6ohY1rPUSd3EQFDqo+kdZ3Fh4aupbdu+99uFQr2A1CBs4uEAjZjIFUMHi4dVxMXzCdCXQj4vBrwVCofl0ulTcv/DAxJJJBUPc8mpoyI2JDw7bFyT+ifTcSubyXytJ51+roWBxwG9Q73WWjZ7eSUU3//nXM0NI+x0PBGrTSgsLS9JFuFxHFrvSqIrJV279gi6tjiVspTza3JjZhY+0CQZj0mlWJSeHTslCro6eFqymCcVVN77kkGjs1p4sy2VOoSlOrFwT+XR+PjkgGaZ+ycKVbRTYUdVrmaImCvzk1dlFCEJdHRJ284+ie/ol0h7p7jFvExcvCCXzp2Rqem3pAMAiqWS6JGYhFI9Mjo6KjevXVUyKEuFHrKpY6JQ8TXT3D8+OTkAHBw6o6LCFo9ag3o4JtlCyTHEt5AxKvS6YUi5kJeZG3Py0NAxlLcJ9xti+K7Mjo/JfGZRuvv6Ze+9+yWEhDZAvzg3JyhX2d6/S7q6e+TimdOS7ElLKBZDwqvmj6rztayr1fVI1IoXi4PAcYZY1tPEEO1wEVlXgRFBDcmIXTqJsS+XyhKLJ5A/OpIVXXptWUYv/UvaenfIocEhMQ2EzHHErlXFCgQl3paU1eVl6QAY8sQTCSmVihKJx1V/ogvgIYF/pACdcMBhqONoHhF88/2d+bojyA6cRvje2IdFjoSjUSnBS8hgyS9lZOzKFdmPxO3o6gQIGzwuDn1dVSCtCKPy1pZXlATXqUsVYMLRmKo87vP4Y1ioqwCdCegmMYx3W/VPn8RrSDwwIMMbcEjkYo29JZVOy+ybI7K4eksODx1VSqvligpReSVLgySM/FI5h2q062jNyL3s7FtoAyGJIlx1225UmwJF6aJRJ3XzHXO9bWvsJa3jQFlBJkz6iuXdu32HzM7MyP0PPNgAU6ko4Qzp6b+flr8MD9OYJg9CwtzL5+T65ITs2bsP3mGxN/ZbBcOn0sk20gAkLQ+huXpFi8vkoY9AoyDjxTR1mbo6Ltt275HpN0dlNxQE40mVM8Ajjxx9VAGhAvQR1akZFCq799ADysMuQqOxh2FNmamEaz51ItGLfFD9+oUJoZkLowHoFA2mljUacqOMflKuVmHpfmnfvlMuvXZeStmMBIMhcWEdjgFJtrUjXI0KchAuAg0ilxLJNoRVBxhIBm0TjjKAuqjTqTs3CQZ6QUUMGFW7eiWMUg6w+yo8YMW7DqtqlZLkUDV2ISfd29KyDwk9MjYmMyOXxQIIKuShqo4VGFNBEgeDQYqVam5N5tEePFQgURIUBCsd1EWd1XrtDUUMLARD9bKaK5ytQ2Gb75g8WMiEP6VkfnZGevv6UF1vSBW5E0PFDAweFRvlfun8WVmamhDNrkmweQ0pwaPt6M4m8mgKTTFXqcrV0ZH1FKBg6qAu6qTuJiCV1Cp2Q0NDr9Uq5Ym+oMEDlSewsoRwrVBEaij7AJ4s7zrOpumxEdm15y6558GHJVe1Zezy6zJx6aJkpq5JFB4z6zVZmBiX1VWUP0IY4CFMYcpQdZ3xqIs6oftCE5DHKwd0q/tzOV8svdDb3nk8VnG9qmgQC0ZURz8Ur91alXgSByZ6ES9kZZTr/PR16UOCh+7dq0CWyyXJ4xqCQ0nKt9YQSlPue2gAeYZzD7yNLk0wmqAreb2WYSxAJ8Dget64wxtEBlDaqVOn/K5dB67t6+t5MhoMJuc8w8UPKiQ9CQR9JK5czhZAQxPt7TKF3OiAIisUViAD2Lg5d0P2HDgoKeRaW0enyqVwBJcO5fFG5dqa7h406qaeX8384uTZL5w9+UqxhYHFp0YLIYA9ddfu3T+4UJF6Rg+YAc9D0+RoIGP1ULhpWspr10evyK7+ftWTrk9PS/++A9KZSm26cih2mMOErem6n/ZsZwA2TM/MPHXs2LEftnSTbh0Q36mIIbx44cLvOnu3f+xUwbWLmoHTCUlF6g2jBQo/GnFrnGNqSHdvr+rIKGMW1KahwEBdzHft98aNwMr8zd8/NDDwccihc0hLi3GubRjY0Bm6H19fPvnZI4c/fHd7PJ2peXYZ+WQ26JufZELjQ6lbAQtnWre0d3apY8TFIdtAo+Qri6mupsB49lBMC+QXF0YefObZT8j0eKWlswVjEyCCOXHihPGb575VCvVuf3lvetsH9rXF0rla3cnhpoIGjgsUPhR3I4TMKYJQV1Z6WO02aEjHa5mNe3OPW3OPRHVrbXFh9Ocvv/KR1372owx1Pf3005uc35Ddgtd8rsf06IdS5777zZ+mUqmPzjm6TPpmvayZOq4LyATeCzkanmiy4qEuC/yXiO8CSMRzvLs1x9phepLNZl868sy3Pyen/5hd1/EfRvWmuvSWNeaRS/RkPDI4+NjE1NSXEoXlpaNB1zqo20abi59/vu/UfM2pie7WUDVq8l3wTwnskeZ+zTbIQ17KoCzKpGzq2KqX32/roRbh8ePHdUzl0s9/5Rv9n/7go19MxCKfCkZiu3V06wrO5gocxL7Dgd/IEobEMH6rejg+auXidL5Y/vWv/vTX53/y/e/MkGajTH7fOt4RUJOY1df4RdtY6ICFRzqTySOhUOA+3Ai3o31H1ZbnlXBruFmt2iMrudy5xx9//BzWV7nXDBGN2xpjbt/5oGUEdhtO3iD47xZOvm8a5CHvpsV38wsUaMwBWsz3rbK5xr0mzdv2t9Jv/f5vhsF4J+Q63IUAAAAASUVORK5CYII=')
|
||||
const tray = new Tray(icon)
|
||||
tray = new Tray(icon)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Item1', type: 'radio' },
|
||||
|
||||
@@ -43,14 +43,15 @@ describe('context-menu', () => {
|
||||
|
||||
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
|
||||
|
||||
expect(template).toHaveLength(7);
|
||||
expect(template).toHaveLength(8);
|
||||
expect(template[0].id).toBe('run');
|
||||
expect(template[1].type).toBe('separator');
|
||||
expect(template[2].id).toBe('cut');
|
||||
expect(template[3].id).toBe('copy');
|
||||
expect(template[4].id).toBe('paste');
|
||||
expect(template[5].type).toBe('separator');
|
||||
expect(template[6].id).toBe('inspect');
|
||||
expect(template[1].id).toBe('clear_console');
|
||||
expect(template[2].type).toBe('separator');
|
||||
expect(template[3].id).toBe('cut');
|
||||
expect(template[4].id).toBe('copy');
|
||||
expect(template[5].id).toBe('paste');
|
||||
expect(template[6].type).toBe('separator');
|
||||
expect(template[7].id).toBe('inspect');
|
||||
});
|
||||
|
||||
it('creates a default context-menu without inspect in production', () => {
|
||||
@@ -62,7 +63,7 @@ describe('context-menu', () => {
|
||||
mockWindow.webContents.emit('context-menu', null, mockFlags);
|
||||
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
|
||||
|
||||
expect(template).toHaveLength(6);
|
||||
expect(template).toHaveLength(7);
|
||||
});
|
||||
|
||||
it('disables cut/copy/paste if not in editFlags', () => {
|
||||
@@ -75,12 +76,12 @@ describe('context-menu', () => {
|
||||
|
||||
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
|
||||
|
||||
expect(template[2].id).toBe('cut');
|
||||
expect(template[2].enabled).toBe(false);
|
||||
expect(template[3].id).toBe('copy');
|
||||
expect(template[3].id).toBe('cut');
|
||||
expect(template[3].enabled).toBe(false);
|
||||
expect(template[4].id).toBe('paste');
|
||||
expect(template[4].id).toBe('copy');
|
||||
expect(template[4].enabled).toBe(false);
|
||||
expect(template[5].id).toBe('paste');
|
||||
expect(template[5].enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('enables cut/copy/paste if in editFlags', () => {
|
||||
@@ -100,12 +101,12 @@ describe('context-menu', () => {
|
||||
|
||||
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
|
||||
|
||||
expect(template[2].id).toBe('cut');
|
||||
expect(template[2].enabled).toBe(true);
|
||||
expect(template[3].id).toBe('copy');
|
||||
expect(template[3].id).toBe('cut');
|
||||
expect(template[3].enabled).toBe(true);
|
||||
expect(template[4].id).toBe('paste');
|
||||
expect(template[4].id).toBe('copy');
|
||||
expect(template[4].enabled).toBe(true);
|
||||
expect(template[5].id).toBe('paste');
|
||||
expect(template[5].enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+19
-12
@@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,25 +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_WARNING_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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+29
-56
@@ -15,7 +15,22 @@ jest.mock('fs-extra', () => ({
|
||||
existsSync: jest.fn()
|
||||
}));
|
||||
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
describe('files', () => {
|
||||
beforeEach(() => {
|
||||
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValue({
|
||||
filePaths: [ 'my/fake/path' ]
|
||||
});
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
});
|
||||
|
||||
describe('setupFileListeners()', () => {
|
||||
|
||||
it('sets up the listener', () => {
|
||||
@@ -31,8 +46,8 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
describe('showOpenDialog', () => {
|
||||
it('tries to open an "open" dialog', () => {
|
||||
showOpenDialog();
|
||||
it('tries to open an "open" dialog', async () => {
|
||||
await showOpenDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
|
||||
@@ -44,22 +59,10 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('notifies the main window of the event', async () => {
|
||||
showOpenDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
|
||||
await cb('');
|
||||
await showOpenDialog();
|
||||
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -92,12 +95,11 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('handles not getting a path returned', async (done) => {
|
||||
showSaveDialog();
|
||||
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValueOnce({
|
||||
filePaths: []
|
||||
});
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
|
||||
await cb();
|
||||
await showSaveDialog();
|
||||
|
||||
process.nextTick(() => {
|
||||
expect(fs.existsSync).toHaveBeenCalledTimes(0);
|
||||
@@ -106,68 +108,39 @@ describe('files', () => {
|
||||
});
|
||||
|
||||
it('ensures that the target is empty on save', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => true);
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
await showSaveDialog();
|
||||
|
||||
expect(dialog.showMessageBox).toHaveBeenCalled();
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not overwrite files without consent', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => false);
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
|
||||
|
||||
await cb('/my/fake/path');
|
||||
await showSaveDialog();
|
||||
|
||||
expect(dialog.showMessageBox).toHaveBeenCalled();
|
||||
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('does not overwrite files if an error happens', async () => {
|
||||
showSaveDialog();
|
||||
|
||||
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
const mockTarget = {
|
||||
webContents: {
|
||||
send: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => {
|
||||
throw new Error('Nope');
|
||||
});
|
||||
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
|
||||
|
||||
let errored = false;
|
||||
|
||||
try {
|
||||
await cb('/fake/path');
|
||||
await showSaveDialog();
|
||||
} catch (error) {
|
||||
errored = error;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ jest.mock('../../src/utils/check-first-run', () => ({
|
||||
isFirstRun: jest.fn()
|
||||
}));
|
||||
|
||||
const mockDialogResponse = {
|
||||
response: 1
|
||||
};
|
||||
|
||||
describe('first-run', () => {
|
||||
const oldDefaultApp = process.defaultApp;
|
||||
const oldPlatform = process.platform;
|
||||
@@ -15,6 +19,8 @@ describe('first-run', () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'darwin'
|
||||
});
|
||||
|
||||
(dialog.showMessageBox as jest.Mock<any>).mockResolvedValue(mockDialogResponse);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -24,7 +30,7 @@ describe('first-run', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.defaultApp = oldDefaultApp;
|
||||
(process as any).defaultApp = oldDefaultApp;
|
||||
});
|
||||
|
||||
describe('onFirstRunMaybe()', () => {
|
||||
@@ -44,7 +50,7 @@ describe('first-run', () => {
|
||||
|
||||
it(`doesn't run unless required (dev mode)`, () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
|
||||
process.defaultApp = true;
|
||||
(process as any).defaultApp = true;
|
||||
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
|
||||
|
||||
onFirstRunMaybe();
|
||||
@@ -65,21 +71,16 @@ describe('first-run', () => {
|
||||
expect(dialog.showMessageBox).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it(`moves the app when requested to do so`, () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
|
||||
it(`moves the app when requested to do so`, async () => {
|
||||
(isFirstRun as jest.Mock).mockReturnValue(true);
|
||||
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
|
||||
|
||||
onFirstRunMaybe();
|
||||
|
||||
const call = (dialog.showMessageBox as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[1];
|
||||
|
||||
cb(1);
|
||||
|
||||
mockDialogResponse.response = 1;
|
||||
await onFirstRunMaybe();
|
||||
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(0);
|
||||
|
||||
cb(0);
|
||||
|
||||
mockDialogResponse.response = 0;
|
||||
await onFirstRunMaybe();
|
||||
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
+17
-2
@@ -12,6 +12,7 @@ jest.mock('../../src/main/ipc');
|
||||
describe('menu', () => {
|
||||
beforeEach(() => {
|
||||
(electron.app.getName as any).mockReturnValue('Electron Fiddle');
|
||||
(electron.dialog.showOpenDialog as any).mockReturnValue(Promise.resolve({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -77,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', () => {
|
||||
@@ -173,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']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,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];
|
||||
});
|
||||
|
||||
+5
-2
@@ -1,10 +1,12 @@
|
||||
import { FileManager } from './file-manager';
|
||||
import { RemoteLoader } from './remote-loader';
|
||||
import { RunnerMock } from './runner';
|
||||
|
||||
export class AppMock {
|
||||
public setupUnsavedOnChangeListener = jest.fn();
|
||||
public setValues = jest.fn();
|
||||
public getValues = jest.fn(() => ({
|
||||
public replaceFiddle = jest.fn();
|
||||
public setEditorValues = jest.fn();
|
||||
public getEditorValues = jest.fn(() => ({
|
||||
main: 'main-content',
|
||||
renderer: 'renderer-content',
|
||||
html: 'html-content'
|
||||
@@ -18,6 +20,7 @@ export class AppMock {
|
||||
|
||||
public fileManager = new FileManager();
|
||||
public runner = new RunnerMock();
|
||||
public remoteLoader = new RemoteLoader();
|
||||
|
||||
public monaco = {
|
||||
editor: {
|
||||
|
||||
@@ -116,7 +116,6 @@ const app = {
|
||||
quit: jest.fn(),
|
||||
relaunch: jest.fn(),
|
||||
setJumpList: jest.fn(),
|
||||
setName: jest.fn(),
|
||||
requestSingleInstanceLock: jest.fn(),
|
||||
on: jest.fn(),
|
||||
once: jest.fn()
|
||||
@@ -157,8 +156,8 @@ const electronMock = {
|
||||
writeImage: jest.fn()
|
||||
},
|
||||
dialog: {
|
||||
showOpenDialog: jest.fn(),
|
||||
showMessageBox: jest.fn()
|
||||
showOpenDialog: jest.fn(() => Promise.resolve({})),
|
||||
showMessageBox: jest.fn(() => Promise.resolve({}))
|
||||
},
|
||||
ipcMain: new MockIPC(),
|
||||
ipcRenderer: new MockIPC(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export class FileManager {
|
||||
public saveToTemp = jest.fn(() => '/mock/temp/dir');
|
||||
public setFiddle = jest.fn();
|
||||
public cleanup = jest.fn();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
||||
export class RemoteLoader {
|
||||
public loadFiddleFromGist = jest.fn();
|
||||
public loadFiddleFromElectronExample = jest.fn();
|
||||
public fetchGistAndLoad = jest.fn();
|
||||
}
|
||||
@@ -4,4 +4,6 @@ export class MockState {
|
||||
@observable public isWarningDialogShowing = false;
|
||||
@observable public gistId = '';
|
||||
@observable public closedPanels = {};
|
||||
@observable public isConsoleShowing = true;
|
||||
@observable public output = [];
|
||||
}
|
||||
|
||||
+195
-83
@@ -1,10 +1,13 @@
|
||||
import { App } from '../../src/renderer/app';
|
||||
import { AppState } from '../../src/renderer/state';
|
||||
import { EditorBackup } from '../../src/utils/editor-backup';
|
||||
import { ElectronFiddleMock } from '../mocks/electron-fiddle';
|
||||
import { MockState } from '../mocks/state';
|
||||
import { overridePlatform, resetPlatform } from '../utils';
|
||||
|
||||
jest.mock('../../src/renderer/file-manager', () => require('../mocks/file-manager'));
|
||||
jest.mock('../../src/renderer/file-manager', () =>
|
||||
require('../mocks/file-manager')
|
||||
);
|
||||
jest.mock('../../src/renderer/state', () => ({
|
||||
appState: {
|
||||
theme: 'defaultDark',
|
||||
@@ -18,8 +21,8 @@ jest.mock('../../src/renderer/components/header', () => ({
|
||||
jest.mock('../../src/renderer/components/dialogs', () => ({
|
||||
Dialogs: () => 'Dialogs;'
|
||||
}));
|
||||
jest.mock('../../src/renderer/components/editors', () => ({
|
||||
Editors: () => 'Editors;'
|
||||
jest.mock('../../src/renderer/components/output-editors-wrapper', () => ({
|
||||
OutputEditorsWrapper: () => 'OutputEditorsWrapper;'
|
||||
}));
|
||||
|
||||
describe('Editors component', () => {
|
||||
@@ -33,11 +36,11 @@ describe('Editors component', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const app = new App();
|
||||
const result = await app.setup() as HTMLDivElement;
|
||||
const result = (await app.setup()) as HTMLDivElement;
|
||||
app.setupUnsavedOnChangeListener = jest.fn();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(result.innerHTML).toBe('Header;Dialogs;Editors;');
|
||||
expect(result.innerHTML).toBe('Dialogs;Header;OutputEditorsWrapper;');
|
||||
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
|
||||
|
||||
jest.useRealTimers();
|
||||
@@ -54,19 +57,19 @@ describe('Editors component', () => {
|
||||
|
||||
it('does not create a touch bar manager on Windows and Linux', () => {
|
||||
overridePlatform('win32');
|
||||
expect((new App()).touchBarManager).toBeFalsy();
|
||||
expect(new App().touchBarManager).toBeFalsy();
|
||||
|
||||
overridePlatform('linux');
|
||||
expect((new App()).touchBarManager).toBeFalsy();
|
||||
expect(new App().touchBarManager).toBeFalsy();
|
||||
|
||||
resetPlatform();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValues()', () => {
|
||||
describe('getEditorValues()', () => {
|
||||
it('gets values', async () => {
|
||||
const app = new App();
|
||||
const b = await app.getValues({});
|
||||
const b = await app.getEditorValues({});
|
||||
|
||||
expect(b.html).toBe('editor-value');
|
||||
expect(b.main).toBe('editor-value');
|
||||
@@ -81,7 +84,7 @@ describe('Editors component', () => {
|
||||
(window as any).ElectronFiddle.editors.renderer = null;
|
||||
|
||||
const app = new App();
|
||||
const result = await app.getValues({});
|
||||
const result = await app.getEditorValues({});
|
||||
|
||||
expect(result.html).toBe('');
|
||||
expect(result.main).toBe('');
|
||||
@@ -96,7 +99,7 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
let threw = false;
|
||||
try {
|
||||
await app.getValues({});
|
||||
await app.getEditorValues({});
|
||||
} catch (error) {
|
||||
threw = true;
|
||||
}
|
||||
@@ -105,61 +108,57 @@ describe('Editors component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setValues()', () => {
|
||||
it('attempts to set values', () => {
|
||||
describe('replaceFiddle()', () => {
|
||||
it('sets editor values and source info', (done) => {
|
||||
const app = new App();
|
||||
app.setValues({
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = false;
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
renderer: 'renderer-value',
|
||||
preload: ''
|
||||
};
|
||||
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledWith('html-value');
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledWith('renderer-value');
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
|
||||
expect(app.state.gistId).toBe('gistId');
|
||||
expect(app.state.templateName).toBe('templateName');
|
||||
expect(app.state.localPath).toBe('localPath');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('attempts to set values for closed editors', () => {
|
||||
const oldMainEditor = window.ElectronFiddle.editors.main;
|
||||
delete window.ElectronFiddle.editors.main;
|
||||
|
||||
it('unsets state of previous source when called', (done) => {
|
||||
const app = new App();
|
||||
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
|
||||
app.setValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
|
||||
expect((app.state.closedPanels.main as EditorBackup)!.model!.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
|
||||
window.ElectronFiddle.editors.main = oldMainEditor;
|
||||
});
|
||||
|
||||
it('warns when the contents are unsaved, does not proceed if denied', (done) => {
|
||||
const app = new App();
|
||||
(app.state as any) = new MockState();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.localPath = '/fake/path';
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
app.setValues({
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
}).then((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledWith('html-value');
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledWith('main-value');
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledWith('renderer-value');
|
||||
};
|
||||
|
||||
done();
|
||||
});
|
||||
expect(app.state.localPath).toBe('/fake/path');
|
||||
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
@@ -168,32 +167,135 @@ describe('Editors component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when the contents are unsaved, does proceed if allowed', (done) => {
|
||||
it('marks the new Fiddle as Saved', (done) => {
|
||||
const app = new App();
|
||||
(app.state as any) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = false;
|
||||
app.setupUnsavedOnChangeListener = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
app.setValues({
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
}).then((result) => {
|
||||
expect(result).toBe(false);
|
||||
expect((window as any).ElectronFiddle.editors.html.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
expect((window as any).ElectronFiddle.editors.main.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
expect((window as any).ElectronFiddle.editors.renderer.setValue)
|
||||
.toHaveBeenCalledTimes(0);
|
||||
};
|
||||
|
||||
done();
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.state.isUnsaved).toBe(false);
|
||||
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe('when current Fiddle is unsaved and prompt appears', () => {
|
||||
it('takes no action if prompt is rejected', (done) => {
|
||||
const app = new App();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
expect(app.state.gistId).toBe('');
|
||||
expect(app.state.templateName).toBeUndefined();
|
||||
|
||||
app.replaceFiddle({}, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).not.toHaveBeenCalled();
|
||||
expect(app.state.localPath).toBeUndefined();
|
||||
expect(app.state.gistId).toBe('');
|
||||
expect(app.state.templateName).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.warningDialogLastResult = false;
|
||||
app.state.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.isWarningDialogShowing = false;
|
||||
it('sets editor values and source info if prompt is accepted', (done) => {
|
||||
const app = new App();
|
||||
(app.state as Partial<AppState>) = new MockState();
|
||||
app.state.isUnsaved = true;
|
||||
app.state.setWarningDialogTexts = jest.fn();
|
||||
app.setEditorValues = jest.fn();
|
||||
|
||||
const editorValues = {
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
};
|
||||
|
||||
app.replaceFiddle(editorValues, {
|
||||
gistId: 'gistId',
|
||||
templateName: 'templateName',
|
||||
filePath: 'localPath'
|
||||
})
|
||||
.then(() => {
|
||||
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
|
||||
expect(app.state.gistId).toBe('gistId');
|
||||
expect(app.state.templateName).toBe('templateName');
|
||||
expect(app.state.localPath).toBe('localPath');
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(app.state.isWarningDialogShowing).toBe(true);
|
||||
app.state.warningDialogLastResult = true;
|
||||
app.state.isWarningDialogShowing = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEditorValues()', () => {
|
||||
it('attempts to set values', () => {
|
||||
const app = new App();
|
||||
app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.html.setValue
|
||||
).toHaveBeenCalledWith('html-value');
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.main.setValue
|
||||
).toHaveBeenCalledWith('main-value');
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.renderer.setValue
|
||||
).toHaveBeenCalledWith('renderer-value');
|
||||
});
|
||||
|
||||
it('attempts to set values for closed editors', () => {
|
||||
const oldMainEditor = window.ElectronFiddle.editors.main;
|
||||
delete window.ElectronFiddle.editors.main;
|
||||
|
||||
const app = new App();
|
||||
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
|
||||
app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value',
|
||||
});
|
||||
|
||||
expect(
|
||||
(app.state.closedPanels.main as EditorBackup)!.model!.setValue
|
||||
).toHaveBeenCalledWith('main-value');
|
||||
|
||||
window.ElectronFiddle.editors.main = oldMainEditor;
|
||||
});
|
||||
|
||||
it('throws if the Fiddle object is not present', async () => {
|
||||
(window as any).ElectronFiddle = null;
|
||||
@@ -201,26 +303,35 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
let threw = false;
|
||||
try {
|
||||
await app.setValues({ html: '', main: '', renderer: ''});
|
||||
await app.setEditorValues({ html: '', main: '', renderer: '' });
|
||||
} catch (error) {
|
||||
threw = true;
|
||||
}
|
||||
|
||||
expect(threw).toBe(true);
|
||||
});
|
||||
|
||||
it('does not set a value if none passed in', async () => {
|
||||
const app = new App();
|
||||
await app.setEditorValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value'
|
||||
});
|
||||
|
||||
expect(
|
||||
(window as any).ElectronFiddle.editors.renderer.setValue
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupUnsavedOnChangeListener()', () => {
|
||||
it('listens for model change events', async () => {
|
||||
const app = new App();
|
||||
|
||||
await app.setValues({
|
||||
html: 'html-value',
|
||||
main: 'main-value',
|
||||
renderer: 'renderer-value'
|
||||
});
|
||||
app.setupUnsavedOnChangeListener();
|
||||
|
||||
const fn = window.ElectronFiddle.editors!.renderer!.onDidChangeModelContent;
|
||||
const fn = window.ElectronFiddle.editors!.renderer!
|
||||
.onDidChangeModelContent;
|
||||
const call = (fn as jest.Mock<any>).mock.calls[0];
|
||||
const cb = call[0];
|
||||
|
||||
@@ -237,16 +348,16 @@ describe('Editors component', () => {
|
||||
const app = new App();
|
||||
app.setupResizeListener();
|
||||
|
||||
expect(window.addEventListener)
|
||||
.toHaveBeenCalled();
|
||||
expect((window.addEventListener as jest.Mock).mock.calls[0][0])
|
||||
.toBe('resize');
|
||||
expect(window.addEventListener).toHaveBeenCalled();
|
||||
expect((window.addEventListener as jest.Mock).mock.calls[0][0]).toBe(
|
||||
'resize'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupTheme()', () => {
|
||||
it(`adds the current theme's css to the document`, async () => {
|
||||
document.head!.innerHTML = '<style id="fiddle-theme"></style>';
|
||||
document.head!.innerHTML = "<style id='fiddle-theme'></style>";
|
||||
|
||||
const app = new App();
|
||||
await app.setupTheme();
|
||||
@@ -272,8 +383,9 @@ describe('Editors component', () => {
|
||||
--error-color: #df3434;
|
||||
--fonts-common: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
</style>`.replace(/ /gm, ''));
|
||||
// tslint:enable:max-line-length
|
||||
</style>`.replace(/ /gm, '')
|
||||
);
|
||||
// tslint:enable:max-line-length
|
||||
});
|
||||
|
||||
it('removes the dark theme option if required', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BinaryManager } from '../../src/renderer/binary';
|
||||
import { USER_DATA_PATH } from '../../src/renderer/constants';
|
||||
import { overridePlatform, resetPlatform } from '../utils';
|
||||
|
||||
import * as path from 'path';
|
||||
@@ -57,6 +58,55 @@ describe('binary', () => {
|
||||
await binaryManager.remove('v3.0.0');
|
||||
expect(fs.remove).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('attempts to clean up the version\'s associated typedefs', async () => {
|
||||
const fs = require('fs-extra');
|
||||
binaryManager.removeTypeDefsForVersion = jest.fn();
|
||||
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
|
||||
await binaryManager.remove('v3.0.0');
|
||||
expect(fs.remove).toHaveBeenCalled();
|
||||
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retries typedef cleanup upon failure', async () => {
|
||||
const fs = require('fs-extra');
|
||||
binaryManager.removeTypeDefsForVersion = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Bwap bwap');
|
||||
});
|
||||
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
|
||||
await binaryManager.remove('v3.0.0');
|
||||
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalledTimes(4);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeTypeDefsForVersion()', () => {
|
||||
it('removes a version\'s typedefs', async () => {
|
||||
const fs = require('fs-extra');
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
await binaryManager.removeTypeDefsForVersion('v3.0.0');
|
||||
expect(fs.remove).toHaveBeenCalledWith(path.join(USER_DATA_PATH, 'electron-typedef', '3.0.0'));
|
||||
});
|
||||
|
||||
it('throws upon fs failure', async () => {
|
||||
const fs = require('fs-extra');
|
||||
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
|
||||
(fs.remove as jest.Mock<any>).mockImplementation(() => {
|
||||
throw new Error('Bwap bwap');
|
||||
});
|
||||
|
||||
expect.assertions(1);
|
||||
|
||||
try {
|
||||
await binaryManager.removeTypeDefsForVersion('v3.0.0');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error('Bwap bwap'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDownloadedVersions()', () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,20 +5,22 @@ exports[`AddressBar component renders 1`] = `
|
||||
className="address-bar empty"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Blueprint3.InputGroup
|
||||
key="addressbar"
|
||||
leftIcon="geosearch"
|
||||
onChange={[Function]}
|
||||
placeholder="https://gist.github.com/..."
|
||||
rightElement={
|
||||
<Blueprint3.Button
|
||||
disabled={true}
|
||||
icon="cloud-download"
|
||||
onClick={[Function]}
|
||||
text="Load Fiddle"
|
||||
/>
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<fieldset>
|
||||
<Blueprint3.InputGroup
|
||||
key="addressbar"
|
||||
leftIcon="geosearch"
|
||||
onChange={[Function]}
|
||||
placeholder="https://gist.github.com/..."
|
||||
rightElement={
|
||||
<Blueprint3.Button
|
||||
disabled={true}
|
||||
icon="cloud-download"
|
||||
onClick={[Function]}
|
||||
text="Load Fiddle"
|
||||
/>
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -27,6 +27,16 @@ exports[`EditorDropdown component renders 1`] = `
|
||||
shouldDismissPopover={true}
|
||||
text="Renderer Process (renderer.js)"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
disabled={false}
|
||||
icon="eye-off"
|
||||
id="preload"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Preload (preload.js)"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
disabled={false}
|
||||
icon="eye-open"
|
||||
@@ -41,6 +51,7 @@ exports[`EditorDropdown component renders 1`] = `
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
@@ -97,6 +108,16 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
|
||||
shouldDismissPopover={true}
|
||||
text="Renderer Process (renderer.js)"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
disabled={false}
|
||||
icon="eye-off"
|
||||
id="preload"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Preload (preload.js)"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
disabled={false}
|
||||
icon="eye-open"
|
||||
@@ -111,6 +132,7 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
|
||||
@@ -1,62 +1,63 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Publish button component renders 1`] = `
|
||||
<Blueprint3.ButtonGroup
|
||||
className="button-publish"
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
boundary="scrollParent"
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<Blueprint3.Menu>
|
||||
<Blueprint3.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
icon="lock"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Private"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
active={true}
|
||||
disabled={false}
|
||||
icon="unlock"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Public"
|
||||
/>
|
||||
</Blueprint3.Menu>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="click"
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
position="bottom"
|
||||
targetTagName="span"
|
||||
transitionDuration={300}
|
||||
usePortal={true}
|
||||
wrapperTagName="span"
|
||||
<fieldset>
|
||||
<Blueprint3.ButtonGroup
|
||||
className="button-publish"
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
boundary="scrollParent"
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<Blueprint3.Menu>
|
||||
<Blueprint3.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
icon="lock"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Private"
|
||||
/>
|
||||
<Blueprint3.MenuItem
|
||||
active={true}
|
||||
disabled={false}
|
||||
icon="unlock"
|
||||
multiline={false}
|
||||
onClick={[Function]}
|
||||
popoverProps={Object {}}
|
||||
shouldDismissPopover={true}
|
||||
text="Public"
|
||||
/>
|
||||
</Blueprint3.Menu>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="click"
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
position="bottom"
|
||||
targetTagName="span"
|
||||
transitionDuration={300}
|
||||
usePortal={true}
|
||||
wrapperTagName="span"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
icon="unlock"
|
||||
/>
|
||||
</Blueprint3.Popover>
|
||||
<Blueprint3.Button
|
||||
icon="unlock"
|
||||
icon="upload"
|
||||
onClick={[Function]}
|
||||
text="Publish"
|
||||
/>
|
||||
</Blueprint3.Popover>
|
||||
<Blueprint3.Button
|
||||
disabled={false}
|
||||
icon="upload"
|
||||
loading={false}
|
||||
onClick={[Function]}
|
||||
text="Publish"
|
||||
/>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</fieldset>
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -1,23 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Dialogs component renders initially without visible dialogs 1`] = `
|
||||
<div
|
||||
className="dialogs"
|
||||
key="dialogs"
|
||||
>
|
||||
<WarningDialog
|
||||
appState={
|
||||
Object {
|
||||
"isAddVersionDialogShowing": false,
|
||||
"isSettingsShowing": false,
|
||||
"isTokenDialogShowing": false,
|
||||
"warningDialogTexts": Object {
|
||||
"cancel": "",
|
||||
"label": "",
|
||||
"ok": "",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -56,7 +56,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -84,7 +84,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -229,7 +229,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -257,7 +257,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -355,7 +355,7 @@ exports[`Editors component renders 1`] = `
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-tile"
|
||||
key="html"
|
||||
key="preload"
|
||||
style={
|
||||
Object {
|
||||
"bottom": "50%",
|
||||
@@ -364,6 +364,179 @@ exports[`Editors component renders 1`] = `
|
||||
"top": "0%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-window mosaic-drop-target preload"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-toolbar draggable"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<h5>
|
||||
Preload (preload.js)
|
||||
</h5>
|
||||
</div>
|
||||
<div />
|
||||
<div
|
||||
className="mosaic-controls"
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-small"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-maximize"
|
||||
icon="maximize"
|
||||
>
|
||||
<svg
|
||||
data-icon="maximize"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="bp3-button bp3-small"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-cross"
|
||||
icon="cross"
|
||||
>
|
||||
<svg
|
||||
data-icon="cross"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body"
|
||||
>
|
||||
Editor
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body-overlay"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="mosaic-window-additional-actions-bar"
|
||||
/>
|
||||
<div
|
||||
className="mosaic-preview"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-toolbar"
|
||||
>
|
||||
<div
|
||||
className="mosaic-window-title"
|
||||
>
|
||||
Preload (preload.js)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-window-body"
|
||||
>
|
||||
<h4>
|
||||
Preload (preload.js)
|
||||
</h4>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-application"
|
||||
icon="application"
|
||||
>
|
||||
<svg
|
||||
data-icon="application"
|
||||
height={72}
|
||||
viewBox="0 0 20 20"
|
||||
width={72}
|
||||
>
|
||||
<desc>
|
||||
application
|
||||
</desc>
|
||||
<path
|
||||
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="drop-target-container"
|
||||
>
|
||||
<div
|
||||
className="drop-target top"
|
||||
/>
|
||||
<div
|
||||
className="drop-target bottom"
|
||||
/>
|
||||
<div
|
||||
className="drop-target left"
|
||||
/>
|
||||
<div
|
||||
className="drop-target right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-split -column"
|
||||
onMouseDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-split-line"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mosaic-tile"
|
||||
key="html"
|
||||
style={
|
||||
Object {
|
||||
"bottom": "25%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="mosaic-window mosaic-drop-target html"
|
||||
@@ -402,7 +575,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -430,7 +603,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -518,7 +691,7 @@ exports[`Editors component renders 1`] = `
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
"top": "75%",
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -534,7 +707,7 @@ exports[`Editors component renders 1`] = `
|
||||
"bottom": "0%",
|
||||
"left": "50%",
|
||||
"right": "0%",
|
||||
"top": "50%",
|
||||
"top": "75%",
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -575,7 +748,7 @@ exports[`Editors component renders 1`] = `
|
||||
home
|
||||
</desc>
|
||||
<path
|
||||
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 0 0 1.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71z"
|
||||
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 001.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -609,7 +782,7 @@ exports[`Editors component renders 1`] = `
|
||||
maximize
|
||||
</desc>
|
||||
<path
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -637,7 +810,7 @@ exports[`Editors component renders 1`] = `
|
||||
cross
|
||||
</desc>
|
||||
<path
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
|
||||
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -715,7 +888,7 @@ exports[`Editors component renders 1`] = `
|
||||
play
|
||||
</desc>
|
||||
<path
|
||||
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 0 0 5 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
|
||||
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 005 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
@@ -869,8 +1042,12 @@ exports[`Editors component renders a toolbar 1`] = `
|
||||
},
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
"first": "preload",
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"setWarningDialogTexts": [Function],
|
||||
@@ -893,8 +1070,12 @@ exports[`Editors component renders a toolbar 1`] = `
|
||||
},
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
"first": "preload",
|
||||
"second": Object {
|
||||
"direction": "column",
|
||||
"first": "html",
|
||||
"second": "docsDemo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"setWarningDialogTexts": [Function],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user