Compare commits
138
Commits
ls
...
token-protocol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd5ca989be | ||
|
|
33a236edd1 | ||
|
|
f7930fb523 | ||
|
|
88cad66076 | ||
|
|
f0afedf896 | ||
|
|
38234dd0a3 | ||
|
|
83e834bea5 | ||
|
|
38be3cca14 | ||
|
|
4908cc6110 | ||
|
|
ab4a9015d1 | ||
|
|
73ab813efc | ||
|
|
f3570c5069 | ||
|
|
4c564eaa63 | ||
|
|
a289842e32 | ||
|
|
9d697e7a3f | ||
|
|
e96e5345e6 | ||
|
|
7c97bc2ef6 | ||
|
|
d55c5c067d | ||
|
|
083ac3fbc2 | ||
|
|
5c8fe3d6e0 | ||
|
|
f034c5240c | ||
|
|
6421187db4 | ||
|
|
6fb385a95f | ||
|
|
c47fea09ed | ||
|
|
ef17ccd9cb | ||
|
|
3f173aa4d4 | ||
|
|
faf15081e0 | ||
|
|
4306f53c70 | ||
|
|
52a8aac6c7 | ||
|
|
7660192dcb | ||
|
|
53ceeb5600 | ||
|
|
5cd7a57f08 | ||
|
|
e96a3da09b | ||
|
|
90c198d487 | ||
|
|
5b56333516 | ||
|
|
c87f65ca76 | ||
|
|
0f27c8aa7a | ||
|
|
b5880aef46 | ||
|
|
fb46eaf0e4 | ||
|
|
c1040097e3 | ||
|
|
333758b73d | ||
|
|
3c2a4e1b76 | ||
|
|
f3d42c107f | ||
|
|
2ece4dc728 | ||
|
|
dd2975bd0e | ||
|
|
fdf03dff9b | ||
|
|
48fa762f7f | ||
|
|
7cd9a86920 | ||
|
|
db2caaad59 | ||
|
|
3b2cb93d8b | ||
|
|
9822753ffd | ||
|
|
8712907182 | ||
|
|
40055ee069 | ||
|
|
a33f175867 | ||
|
|
107ba63a5a | ||
|
|
b66ad7e247 | ||
|
|
7eb15b6666 | ||
|
|
2ed29f68dd | ||
|
|
d89d595b57 | ||
|
|
2b34ef5e67 | ||
|
|
d6f7146447 | ||
|
|
006ba5f0a5 | ||
|
|
5df390b0fd | ||
|
|
3eb25fed91 | ||
|
|
9b06f792ea | ||
|
|
c116152605 | ||
|
|
b9f8d55809 | ||
|
|
89e697fcd6 | ||
|
|
9df5314c08 | ||
|
|
d01ba1cdff | ||
|
|
ed5ccf133f | ||
|
|
e1decd4819 | ||
|
|
3c9f477b89 | ||
|
|
5cfbf2cab5 | ||
|
|
89272fbaa9 | ||
|
|
5d572f451c | ||
|
|
319136ecc8 | ||
|
|
3a4aa06331 | ||
|
|
e283a118f6 | ||
|
|
e67d1b0179 | ||
|
|
9805d98dd8 | ||
|
|
009ad25983 | ||
|
|
9a65124289 | ||
|
|
4d7c81ee69 | ||
|
|
ed91cb5ba1 | ||
|
|
8b04f67f71 | ||
|
|
2e183f0538 | ||
|
|
6b022bbea7 | ||
|
|
6bdb7d71ec | ||
|
|
0ebe6ab9b3 | ||
|
|
5e73deeb4f | ||
|
|
02d81aa407 | ||
|
|
46deeb857e | ||
|
|
ca55d2e620 | ||
|
|
88974d277f | ||
|
|
1239afafeb | ||
|
|
415877c067 | ||
|
|
0a0779ffa1 | ||
|
|
9cf34ea983 | ||
|
|
83a7d23b38 | ||
|
|
7d19ad91a6 | ||
|
|
db13699790 | ||
|
|
4a8fc24ff6 | ||
|
|
3ed41626b1 | ||
|
|
b3e25cfe79 | ||
|
|
00bb15a891 | ||
|
|
94da51128a | ||
|
|
b573f09494 | ||
|
|
f05e36de34 | ||
|
|
44c7c70793 | ||
|
|
bcbca7aceb | ||
|
|
9515458545 | ||
|
|
219e90cad8 | ||
|
|
ba23c8019d | ||
|
|
2fef96a2b2 | ||
|
|
2d8cb9748d | ||
|
|
eaefbb7ad0 | ||
|
|
d5f28253f8 | ||
|
|
ac451d6b4a | ||
|
|
b5e0e63766 | ||
|
|
49538ea55a | ||
|
|
23ac9b8e02 | ||
|
|
4bf2bee7c8 | ||
|
|
e742d8426c | ||
|
|
6bdc995eb6 | ||
|
|
72cbcd46d1 | ||
|
|
f1e51c71a3 | ||
|
|
95e88c0eee | ||
|
|
a54037ef0e | ||
|
|
83bb6fc73e | ||
|
|
f8f1f0ec08 | ||
|
|
f7a19b92a8 | ||
|
|
dc76b78337 | ||
|
|
f6f3e94a97 | ||
|
|
89cede25b1 | ||
|
|
8e8eec3cc6 | ||
|
|
a0d8ca4019 | ||
|
|
1f0581afbc |
+1
-1
@@ -1,5 +1,5 @@
|
||||
language: node_js
|
||||
node_js: "8"
|
||||
node_js: "12"
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
@@ -15,13 +15,13 @@ address bar.
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/378023/49716908-7d2b0500-fc98-11e8-8f52-def504802509.png" width="880px" alt="Electron Fiddle screenshot">
|
||||
<img src="https://user-images.githubusercontent.com/1426799/52155868-d3357c80-2639-11e9-9496-fa97b1dc7897.jpg" width="880px" alt="Electron Fiddle screenshot">
|
||||
|
||||
# Features
|
||||
|
||||
### Explore Electron
|
||||
|
||||

|
||||

|
||||
|
||||
Try Electron without installing any dependencies: Fiddle includes everything
|
||||
you'll need to explore the platform. It also includes examples for every API
|
||||
@@ -40,7 +40,7 @@ all Electron APIs only a few keystrokes away.
|
||||
|
||||
### Compile and Package
|
||||
|
||||

|
||||

|
||||
|
||||
Fiddle can automatically turn your experiment into binaries you can share with
|
||||
your friends, coworkers, or grandparents. It does so thanks to
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"bail": true,
|
||||
"resetMocks": true,
|
||||
"resetModules": true,
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/tests/setup.js",
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/tests/setup.js"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"jsx",
|
||||
|
||||
Generated
+4886
-4663
File diff suppressed because it is too large
Load Diff
+54
-52
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "electron-fiddle",
|
||||
"productName": "Electron Fiddle",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"description": "The easiest way to get started with Electron",
|
||||
"repository": "https://github.com/electron/fiddle",
|
||||
"main": "./dist/src/main/main",
|
||||
@@ -11,7 +11,7 @@
|
||||
"lint:style": "stylelint ./src/less/*.less --fix",
|
||||
"lint:ts": "tslint -c tslint.json -p tsconfig.json -e \"node_modules/**/*.ts\" --fix",
|
||||
"lint:tests": "tslint ./tests/**/*.ts{,x} -c tslint.json --fix",
|
||||
"lint:templates": "standard ./static/templates/**/*.js",
|
||||
"lint:templates": "standard ./static/show-me/**/*.js",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"make": "electron-forge make",
|
||||
"package": "electron-forge package",
|
||||
@@ -36,71 +36,73 @@
|
||||
"forge": "./forge.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.10.0",
|
||||
"@blueprintjs/select": "^3.4.0",
|
||||
"@octokit/rest": "^15.12.1",
|
||||
"builtin-modules": "^3.0.0",
|
||||
"@blueprintjs/core": "^3.15.1",
|
||||
"@blueprintjs/select": "^3.8.0",
|
||||
"@octokit/rest": "^16.25.6",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"electron-default-menu": "^1.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"extract-zip": "^1.6.7",
|
||||
"fix-path": "^2.1.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"mobx": "^5.8.0",
|
||||
"mobx-react": "^5.4.3",
|
||||
"monaco-loader": "^0.15.0",
|
||||
"namor": "^1.1.1",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-mosaic-component": "^2.0.2",
|
||||
"semver": "^5.6.0",
|
||||
"tmp": "0.0.33",
|
||||
"fs-extra": "^8.0.1",
|
||||
"mobx": "^5.9.4",
|
||||
"mobx-react": "^5.4.4",
|
||||
"monaco-editor": "^0.17.0",
|
||||
"monaco-loader": "1.0.0",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-mosaic-component": "^2.1.0",
|
||||
"semver": "^6.0.0",
|
||||
"tmp": "0.1.0",
|
||||
"tslib": "^1.9.3",
|
||||
"update-electron-app": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.32",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.32",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.32",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.32",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.32",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.32",
|
||||
"@types/builtin-modules": "^2.0.0",
|
||||
"@babel/core": "^7.4.4",
|
||||
"@electron-forge/cli": "^6.0.0-beta.34",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.39",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.39",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.34",
|
||||
"@types/builtin-modules": "^3.1.1",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/enzyme": "^3.1.13",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/log-symbols": "^2.0.0",
|
||||
"@types/enzyme": "^3.9.2",
|
||||
"@types/fs-extra": "^7.0.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/log-symbols": "^3.0.0",
|
||||
"@types/node": "^10.11.1",
|
||||
"@types/react": "^16.7.18",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/semver": "^5.5.0",
|
||||
"@types/tmp": "0.0.33",
|
||||
"chokidar": "^2.0.4",
|
||||
"coveralls": "^3.0.2",
|
||||
"electron": "4.0.2",
|
||||
"enzyme": "^3.6.0",
|
||||
"enzyme-adapter-react-16": "^1.5.0",
|
||||
"@types/react": "^16.8.17",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/tmp": "0.1.0",
|
||||
"chokidar": "^2.1.6",
|
||||
"coveralls": "^3.0.3",
|
||||
"electron": "5.0.1",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.13.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"jest": "^23.6.0",
|
||||
"jest-fetch-mock": "^1.6.6",
|
||||
"less": "^3.8.1",
|
||||
"log-symbols": "^2.2.0",
|
||||
"monaco-editor": "^0.15.6",
|
||||
"node-abi": "^2.5.1",
|
||||
"node-fetch": "^2.3.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest-fetch-mock": "^2.1.2",
|
||||
"less": "^3.9.0",
|
||||
"log-symbols": "^3.0.0",
|
||||
"node-abi": "^2.8.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.11.0",
|
||||
"react-test-renderer": "^16.7.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"rimraf": "^2.6.3",
|
||||
"standard": "^12.0.1",
|
||||
"stylelint": "^9.9.0",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"ts-jest": "^23.10.5",
|
||||
"tslint": "^5.12.0",
|
||||
"tslint-microsoft-contrib": "^6.0.0",
|
||||
"tslint-react": "^3.6.0",
|
||||
"typescript": "^3.2.2"
|
||||
"stylelint": "^10.0.1",
|
||||
"stylelint-config-standard": "^18.3.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.16.0",
|
||||
"tslint-microsoft-contrib": "^6.1.1",
|
||||
"tslint-react": "^4.0.0",
|
||||
"typescript": "^3.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
+28
-1
@@ -32,6 +32,12 @@ export interface ElectronVersion extends NpmVersion {
|
||||
source: ElectronVersionSource;
|
||||
}
|
||||
|
||||
export interface SetFiddleOptions {
|
||||
values: EditorValues;
|
||||
filePath?: string;
|
||||
templateName?: string;
|
||||
}
|
||||
|
||||
export interface OutputEntry {
|
||||
text: string;
|
||||
timestamp: number;
|
||||
@@ -53,6 +59,27 @@ export interface Templates {
|
||||
[index: string]: string | Templates;
|
||||
}
|
||||
|
||||
export type EditorId = 'main' | 'renderer' | 'html';
|
||||
// Editors
|
||||
export const enum EditorId {
|
||||
'main' = 'main',
|
||||
'renderer' = 'renderer',
|
||||
'html' = 'html'
|
||||
}
|
||||
|
||||
// Panels that can show up as a mosaic
|
||||
export const enum PanelId {
|
||||
'docsDemo' = 'docsDemo'
|
||||
}
|
||||
|
||||
export type MosaicId = EditorId | PanelId;
|
||||
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.html ];
|
||||
export const ALL_PANELS = [ PanelId.docsDemo ];
|
||||
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
|
||||
|
||||
export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
|
||||
|
||||
export const enum DocsDemoPage {
|
||||
DEFAULT = 'DEFAULT',
|
||||
DEMO_APP = 'DEMO_APP'
|
||||
}
|
||||
|
||||
+8
-2
@@ -1,6 +1,7 @@
|
||||
export enum IpcEvents {
|
||||
OPEN_SETTINGS = 'OPEN_SETTINGS',
|
||||
LOAD_GIST_REQUEST = 'LOAD_GIST_REQUEST',
|
||||
LOAD_ELECTRON_EXAMPLE_REQUEST = 'LOAD_ELECTRON_EXAMPLE_REQUEST',
|
||||
FIDDLE_RUN = 'FIDDLE_RUN',
|
||||
FIDDLE_PACKAGE = 'FIDDLE_PACKAGE',
|
||||
FIDDLE_MAKE = 'FIDDLE_MAKE',
|
||||
@@ -16,7 +17,8 @@ export enum IpcEvents {
|
||||
FS_SAVE_FIDDLE_DIALOG = 'FS_SAVE_FIDDLE_DIALOG',
|
||||
FS_SAVE_FIDDLE_ERROR = 'FS_SAVE_FIDDLE_ERROR',
|
||||
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
|
||||
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR'
|
||||
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
|
||||
SET_GITHUB_TOKEN = 'SET_GITHUB_TOKEN'
|
||||
}
|
||||
|
||||
export const ipcMainEvents = [
|
||||
@@ -28,6 +30,7 @@ export const ipcMainEvents = [
|
||||
export const ipcRendererEvents = [
|
||||
IpcEvents.OPEN_SETTINGS,
|
||||
IpcEvents.LOAD_GIST_REQUEST,
|
||||
IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
|
||||
IpcEvents.FIDDLE_RUN,
|
||||
IpcEvents.FIDDLE_PACKAGE,
|
||||
IpcEvents.FIDDLE_MAKE,
|
||||
@@ -40,5 +43,8 @@ export const ipcRendererEvents = [
|
||||
IpcEvents.FS_SAVE_FIDDLE_AS,
|
||||
IpcEvents.FS_SAVE_FIDDLE_FORGE,
|
||||
IpcEvents.FS_SAVE_FIDDLE_GIST,
|
||||
IpcEvents.SHOW_WELCOME_TOUR
|
||||
IpcEvents.SHOW_WELCOME_TOUR,
|
||||
IpcEvents.SET_GITHUB_TOKEN
|
||||
];
|
||||
|
||||
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
background-color: @background-1;
|
||||
}
|
||||
|
||||
.bp3-button.bp3-minimal {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.bp3-button:hover,
|
||||
.bp3-button.bp3-minimal:hover {
|
||||
background-color: rgba(138, 155, 168, 0.15);
|
||||
}
|
||||
|
||||
.bp3-menu-item.bp3-active.bp3-intent-primary {
|
||||
background-color: @foreground-3;
|
||||
}
|
||||
@@ -29,4 +38,8 @@
|
||||
background-color: @background-3;
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-running-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,28 @@
|
||||
animation: fadein 0.15s;
|
||||
}
|
||||
|
||||
.mosaic-window-toolbar {
|
||||
& > div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 5px 0 5px;
|
||||
|
||||
.mosaic-controls {
|
||||
transform: scale(0.75);
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "../variables.less";
|
||||
|
||||
.fiddle {
|
||||
.mosaic-window-toolbar.draggable {
|
||||
.mosaic-window-toolbar {
|
||||
background: var(--background-1);
|
||||
box-shadow: inset 0 0 100px 100px rgba(53, 53, 53, 0.1);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
@@ -34,9 +34,9 @@
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch;
|
||||
align-content: stretch;
|
||||
font-size: 12px;
|
||||
overflow: visible;
|
||||
z-index: 4;
|
||||
background-color: @background-4;
|
||||
}
|
||||
|
||||
.mosaic {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.show-me-panel {
|
||||
padding: 10px;
|
||||
overflow: scroll;
|
||||
|
||||
& > *:first-child {
|
||||
margin-top: 0;
|
||||
};
|
||||
|
||||
.bp3-callout {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.show-me-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
max-height: 400px;
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,6 @@
|
||||
|
||||
// Variables, also imported in each file
|
||||
@import "variables.less";
|
||||
|
||||
@import "main.less";
|
||||
@import "container.less";
|
||||
@import "mosaic-vendor.less";
|
||||
@@ -18,3 +17,4 @@
|
||||
@import "components/editors.less";
|
||||
@import "components/tour.less";
|
||||
@import "components/commands-version-chooser.less";
|
||||
@import "components/show-me.less";
|
||||
|
||||
@@ -136,17 +136,17 @@ export function createContextMenu(browserWindow: BrowserWindow) {
|
||||
{
|
||||
id: 'cut',
|
||||
label: 'Cut',
|
||||
role: editFlags.canCut ? 'cut' : '',
|
||||
role: 'cut',
|
||||
enabled: editFlags.canCut
|
||||
}, {
|
||||
id: 'copy',
|
||||
label: 'Copy',
|
||||
role: editFlags.canCopy ? 'copy' : '',
|
||||
role: 'copy',
|
||||
enabled: editFlags.canCopy
|
||||
}, {
|
||||
id: 'paste',
|
||||
label: 'Paste',
|
||||
role: editFlags.canPaste ? 'paste' : '',
|
||||
role: 'paste',
|
||||
enabled: editFlags.canPaste
|
||||
}, {
|
||||
type: 'separator'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
|
||||
/**
|
||||
* Installs developer tools if we're in dev mode.
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function setupDevTools(): Promise<void> {
|
||||
if (!isDevMode) return;
|
||||
|
||||
const {
|
||||
default: installExtension,
|
||||
REACT_DEVELOPER_TOOLS,
|
||||
REACT_PERF
|
||||
} = require('electron-devtools-installer');
|
||||
|
||||
try {
|
||||
const react = await installExtension(REACT_DEVELOPER_TOOLS);
|
||||
console.log(`installDevTools: Installed ${react}`);
|
||||
|
||||
const perf = await installExtension(REACT_PERF);
|
||||
console.log(`installDevTools: Installed ${perf}`);
|
||||
} catch (error) {
|
||||
console.warn(`installDevTools: Error occured:`, error);
|
||||
}
|
||||
}
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
import { IpcEvents, ipcMainEvents } from '../ipc-events';
|
||||
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
|
||||
/**
|
||||
@@ -12,12 +12,25 @@ import { getOrCreateMainWindow } from './windows';
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
export class IpcMainManager extends EventEmitter {
|
||||
public readyWebContents = new WeakSet<Electron.WebContents>();
|
||||
private messageQueue = new WeakMap<Electron.WebContents, Array<[IpcEvents, Array<any> | undefined]>>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
ipcMainEvents.forEach((name) => {
|
||||
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
|
||||
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.Event) => {
|
||||
this.readyWebContents.add(event.sender);
|
||||
const queue = this.messageQueue.get(event.sender);
|
||||
this.messageQueue.delete(event.sender);
|
||||
if (!queue) return;
|
||||
for (const item of queue) {
|
||||
this.send(item[0], item[1], event.sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +44,11 @@ export class IpcMainManager extends EventEmitter {
|
||||
public send(channel: IpcEvents, args?: Array<any>, target?: Electron.WebContents) {
|
||||
const _target = target || getOrCreateMainWindow().webContents;
|
||||
const _args = args || [];
|
||||
if (!this.readyWebContents.has(_target)) {
|
||||
const existing = this.messageQueue.get(_target) || [];
|
||||
this.messageQueue.set(_target, [...existing, [channel, args]]);
|
||||
return;
|
||||
}
|
||||
|
||||
_target.send(channel, ..._args);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { setupAboutPanel } from '../utils/set-about-panel';
|
||||
import { setupDevTools } from './devtools';
|
||||
import { setupDialogs } from './dialogs';
|
||||
import { onFirstRunMaybe } from './first-run';
|
||||
import { listenForProtocolHandler, setupProtocolHandler } from './protocol';
|
||||
@@ -17,6 +19,7 @@ export async function onReady() {
|
||||
if (!isDevMode()) process.env.NODE_ENV = 'production';
|
||||
|
||||
getOrCreateMainWindow();
|
||||
setupAboutPanel();
|
||||
|
||||
const { setupMenu } = await import('./menu');
|
||||
const { setupFileListeners } = await import('./files');
|
||||
@@ -26,6 +29,16 @@ export async function onReady() {
|
||||
setupFileListeners();
|
||||
setupUpdates();
|
||||
setupDialogs();
|
||||
setupDevTools();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the "before-quit" event
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export function onBeforeQuit() {
|
||||
(global as any).isQuitting = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,6 +75,7 @@ export function main() {
|
||||
|
||||
// Launch
|
||||
app.on('ready', onReady);
|
||||
app.on('before-quit', onBeforeQuit);
|
||||
app.on('window-all-closed', onWindowsAllClosed);
|
||||
app.on('activate', getOrCreateMainWindow);
|
||||
}
|
||||
|
||||
+43
-4
@@ -16,17 +16,56 @@ const handlePotentialProtocolLaunch = (url: string) => {
|
||||
}
|
||||
|
||||
const parsed = nodeUrl.parse(url.replace(/\/$/, ''));
|
||||
if (!parsed.pathname) return;
|
||||
if (!parsed.pathname || !parsed.hostname) return;
|
||||
|
||||
const pathParts = parsed.pathname.split('/');
|
||||
if (pathParts.length <= 0 || pathParts.length > 2) return;
|
||||
const pathParts = parsed.pathname.split('/').slice(1);
|
||||
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [parsed.pathname]);
|
||||
switch (parsed.hostname) {
|
||||
// electron-fiddle://gist/blub
|
||||
case 'gist':
|
||||
if (pathParts.length === 1) {
|
||||
// We only have a gist ID
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
|
||||
id: pathParts[0],
|
||||
}]);
|
||||
} else if (pathParts.length === 2) {
|
||||
// We have a gist owner and gist ID, we can ignore the owner
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
|
||||
id: pathParts[1],
|
||||
}]);
|
||||
} else {
|
||||
// This is a super invalid gist launch
|
||||
return;
|
||||
}
|
||||
break;
|
||||
// electron-fiddle://electron/{ref}/{path}
|
||||
case 'electron':
|
||||
if (pathParts.length > 1) {
|
||||
// First part of the commit HASH / ref / branch
|
||||
// Rest is the path to the example
|
||||
ipcMainManager.send(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, [{
|
||||
ref: pathParts[0],
|
||||
path: pathParts.slice(1).join('/'),
|
||||
}]);
|
||||
} else {
|
||||
// This is an invalid electron launch
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'token':
|
||||
if (pathParts.length === 1) {
|
||||
// We have a token
|
||||
ipcMainManager.send(IpcEvents.SET_GITHUB_TOKEN, [pathParts[0]]);
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export const scanArgv = (argv: Array<string>) => {
|
||||
const protocolArg = argv.find((arg) => arg.startsWith(`${PROTOCOL}://`));
|
||||
if (protocolArg) {
|
||||
console.info('Found protocol arg in argv:', protocolArg);
|
||||
handlePotentialProtocolLaunch(protocolArg);
|
||||
}
|
||||
};
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
|
||||
acceptFirstMouse: true,
|
||||
backgroundColor: '#1d2427',
|
||||
webPreferences: {
|
||||
webviewTag: false
|
||||
webviewTag: false,
|
||||
nodeIntegration: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+26
-16
@@ -1,13 +1,18 @@
|
||||
import { when } from 'mobx';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ALL_EDITORS, EditorId, EditorValues } from '../interfaces';
|
||||
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { updateEditorLayout } from '../utils/editor-layout';
|
||||
import { getEditorValue } from '../utils/editor-value';
|
||||
import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
|
||||
import { isEditorBackup } from '../utils/type-checks';
|
||||
import { FileManager } from './file-manager';
|
||||
import { Runner } from './runner';
|
||||
import { appState } from './state';
|
||||
import { getTheme } from './themes';
|
||||
import { TouchBarManager } from './touch-bar-manager';
|
||||
|
||||
/**
|
||||
* The top-level class controlling the whole app. This is *not* a React component,
|
||||
@@ -21,10 +26,15 @@ export class App {
|
||||
public state = appState;
|
||||
public fileManager = new FileManager(appState);
|
||||
public runner = new Runner(appState);
|
||||
public touchBarManager: TouchBarManager | undefined;
|
||||
|
||||
constructor() {
|
||||
this.getValues = this.getValues.bind(this);
|
||||
this.setValues = this.setValues.bind(this);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
this.touchBarManager = new TouchBarManager(appState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,18 +59,17 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
const { main, html, renderer } = fiddle.editors;
|
||||
for (const name of ALL_EDITORS) {
|
||||
const editor = fiddle.editors[name];
|
||||
const backup = this.state.closedPanels[name];
|
||||
|
||||
if (html && html.setValue && values.html) {
|
||||
html.setValue(values.html);
|
||||
}
|
||||
|
||||
if (main && main.setValue && values.main) {
|
||||
main.setValue(values.main);
|
||||
}
|
||||
|
||||
if (renderer && renderer.setValue && values.renderer) {
|
||||
renderer.setValue(values.renderer);
|
||||
if (isEditorBackup(backup) && backup.model && values[name]) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
} else if (editor && editor.setValue && values[name]) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
}
|
||||
}
|
||||
|
||||
this.state.isUnsaved = false;
|
||||
@@ -81,11 +90,10 @@ export class App {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
const { main, html, renderer } = fiddle.editors;
|
||||
const values: EditorValues = {
|
||||
html: html && html.getValue() ? html.getValue() : '',
|
||||
main: main && main.getValue() ? main.getValue() : '',
|
||||
renderer: renderer && renderer.getValue() ? renderer.getValue() : '',
|
||||
html: getEditorValue(EditorId.html),
|
||||
main: getEditorValue(EditorId.main),
|
||||
renderer: getEditorValue(EditorId.renderer),
|
||||
};
|
||||
|
||||
if (options && options.include !== false) {
|
||||
@@ -121,6 +129,8 @@ export class App {
|
||||
|
||||
this.setupResizeListener();
|
||||
|
||||
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
|
||||
|
||||
// Todo: A timer here is terrible. Let's fix this
|
||||
// and ensure we actually do it once Editors have mounted.
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -171,7 +171,7 @@ export class BinaryManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips an electron package so that we can actaully use it.
|
||||
* Unzips an electron package so that we can actually use it.
|
||||
*
|
||||
* @param {string} zipPath
|
||||
* @param {string} extractPath
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Button, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import { reaction } from 'mobx';
|
||||
import { reaction, when } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { EditorId, EditorValues } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
|
||||
import { getTitle } from '../../utils/get-title';
|
||||
import { idFromUrl, urlFromId } from '../../utils/gist';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { getContent } from '../content';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface AddressBarProps {
|
||||
@@ -23,9 +27,14 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
constructor(props: AddressBarProps) {
|
||||
super(props);
|
||||
|
||||
this.loadFiddle = this.loadFiddle.bind(this);
|
||||
this.fetchExampleAndLoad = this.fetchExampleAndLoad.bind(this);
|
||||
this.fetchGistAndLoad = this.fetchGistAndLoad.bind(this);
|
||||
this.loadFiddleFromGist = this.loadFiddleFromGist.bind(this);
|
||||
this.loadFiddleFromElectronExample = this.loadFiddleFromElectronExample.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.verifyRemoteLoad = this.verifyRemoteLoad.bind(this);
|
||||
|
||||
const { gistId } = this.props.appState;
|
||||
const value = gistId ? urlFromId(gistId) : '';
|
||||
@@ -39,15 +48,19 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
*
|
||||
* @param {React.SyntheticEvent<HTMLFormElement>} event
|
||||
*/
|
||||
public async handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
|
||||
public handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
this.submit();
|
||||
}
|
||||
|
||||
this.props.appState.gistId = idFromUrl(this.state.value) || this.state.value;
|
||||
|
||||
console.log('Loading');
|
||||
|
||||
/**
|
||||
* Commit the address bar's value to app state and load the fiddle.
|
||||
*
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public submit() {
|
||||
if (this.state.value) {
|
||||
this.loadFiddle();
|
||||
this.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +72,44 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
() => this.props.appState.gistId,
|
||||
(gistId: string) => this.setState({ value: urlFromId(gistId) })
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies from the user that we should be loading this fiddle
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
appState.setWarningDialogTexts({
|
||||
label: `Are you sure you sure you want to load this ${what}? Only load and run it if you trust the source`
|
||||
});
|
||||
appState.isWarningDialogShowing = true;
|
||||
await when(() => !appState.isWarningDialogShowing);
|
||||
|
||||
return !!appState.warningDialogLastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,39 +121,83 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
this.setState({ value: event.target.value });
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
try {
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
// TODO: Maybe we should fetch the package.json for the given ref to use the correct version of Electron?
|
||||
const folder = await octo.repos.getContents({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
|
||||
const values = {
|
||||
html: await getContent(EditorId.html, appState.version),
|
||||
renderer: await getContent(EditorId.renderer, appState.version),
|
||||
main: await getContent(EditorId.main, appState.version),
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
if (!Array.isArray(folder.data)) {
|
||||
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(loaders);
|
||||
|
||||
return this.handleLoadingSuccess(values, '');
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fiddle
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof AddressBar
|
||||
*/
|
||||
public async loadFiddle(): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit();
|
||||
const gist = await octo.gists.get({
|
||||
gist_id: appState.gistId
|
||||
});
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const gist = await octo.gists.get({ gist_id: gistId });
|
||||
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues({
|
||||
return this.handleLoadingSuccess({
|
||||
html: gist.data.files[INDEX_HTML_NAME].content,
|
||||
main: gist.data.files[MAIN_JS_NAME].content,
|
||||
renderer: gist.data.files[RENDERER_JS_NAME].content,
|
||||
});
|
||||
|
||||
document.title = getTitle(appState);
|
||||
appState.localPath = null;
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
|
||||
@@ -111,7 +206,7 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
disabled={!isValueCorrect}
|
||||
icon='cloud-download'
|
||||
text='Load Fiddle'
|
||||
onClick={this.loadFiddle}
|
||||
onClick={this.submit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -136,4 +231,49 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub succeeded, let's move on.
|
||||
*
|
||||
* @private
|
||||
* @param {Partial<EditorValues>} values
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
|
||||
appState.setWarningDialogTexts({
|
||||
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
|
||||
});
|
||||
|
||||
await window.ElectronFiddle.app.setValues(values);
|
||||
|
||||
document.title = getTitle(appState);
|
||||
appState.gistId = gistId;
|
||||
appState.localPath = undefined;
|
||||
appState.templateName = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading a fiddle from GitHub failed - this method handles this case
|
||||
* gracefully.
|
||||
*
|
||||
* @private
|
||||
* @param {Error} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
this.props.appState.setWarningDialogTexts({
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
|
||||
this.props.appState.toogleWarningDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Button, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ALL_EDITORS, MosaicId, PanelId } from '../../interfaces';
|
||||
import { getVisibleMosaics } from '../../utils/editors-mosaic-arrangement';
|
||||
import { AppState } from '../state';
|
||||
import { TITLE_MAP } from './editors';
|
||||
|
||||
export interface EditorDropdownState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface EditorDropdownProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dropdown allowing users to toggle the various editors
|
||||
*
|
||||
* @class EditorDropdown
|
||||
* @extends {React.Component<EditorDropdownProps, EditorDropdownState>}
|
||||
*/
|
||||
@observer
|
||||
export class EditorDropdown extends React.Component<EditorDropdownProps, EditorDropdownState> {
|
||||
constructor(props: EditorDropdownProps) {
|
||||
super(props);
|
||||
|
||||
this.onItemClick = this.onItemClick.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Popover content={this.renderMenu()} position={Position.BOTTOM}>
|
||||
<Button icon='applications' text='Editors' />
|
||||
</Popover>
|
||||
{this.renderDocsDemos()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderDocsDemos() {
|
||||
if (!process.env.FIDDLE_DOCS_DEMOS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon='help'
|
||||
text='Docs & Demos'
|
||||
id={PanelId.docsDemo}
|
||||
onClick={this.onItemClick}
|
||||
active={!this.props.appState.closedPanels.docsDemo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderMenu() {
|
||||
return (
|
||||
<Menu>
|
||||
{...this.renderMenuItems()}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
public renderMenuItems() {
|
||||
const { appState } = this.props;
|
||||
const result: Array<JSX.Element> = [];
|
||||
const visibleMosaics = getVisibleMosaics(appState.mosaicArrangement);
|
||||
|
||||
for (const id of ALL_EDITORS) {
|
||||
result.push(
|
||||
<MenuItem
|
||||
icon={visibleMosaics.includes(id) ? 'eye-open' : 'eye-off'}
|
||||
key={id}
|
||||
text={TITLE_MAP[id]}
|
||||
id={id}
|
||||
onClick={this.onItemClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public onItemClick(event: React.MouseEvent) {
|
||||
const { id } = event.currentTarget;
|
||||
const { appState } = this.props;
|
||||
const visibleMosaics = getVisibleMosaics(appState.mosaicArrangement);
|
||||
|
||||
if (visibleMosaics.includes(id as MosaicId)) {
|
||||
console.log(`EditorDropdown: Closing ${id}`);
|
||||
appState.hideAndBackupMosaic(id as MosaicId);
|
||||
} else {
|
||||
console.log(`EditorDropdown: Opening ${id}`);
|
||||
appState.showMosaic(id as MosaicId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,12 +73,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
|
||||
public async publishFiddle(): Promise<void> {
|
||||
this.setState({ isPublishing: true });
|
||||
|
||||
const octo = await getOctokit();
|
||||
octo.authenticate({
|
||||
type: 'token',
|
||||
token: this.props.appState.gitHubToken!
|
||||
});
|
||||
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const { gitHubPublishAsPublic } = this.props.appState;
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getValues(options);
|
||||
|
||||
@@ -48,6 +48,6 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
|
||||
props.icon = <Spinner size={16} />;
|
||||
}
|
||||
|
||||
return <Button {...props} />;
|
||||
return <Button {...props} type={undefined} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +121,26 @@ export class VersionChooser extends React.Component<VersionChooserProps, Version
|
||||
}
|
||||
|
||||
public getItems(): Array<ElectronVersion> {
|
||||
const { versions, versionsToShow } = this.props.appState;
|
||||
const { versions, versionsToShow, statesToShow } = this.props.appState;
|
||||
|
||||
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
|
||||
.filter((item) => !!item && versionsToShow.includes(getReleaseChannel(item)));
|
||||
.filter((item) => {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the version
|
||||
if (!versionsToShow.includes(getReleaseChannel(item))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the state
|
||||
if (!statesToShow.includes(item.state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { AddressBar } from './commands-address-bar';
|
||||
import { EditorDropdown } from './commands-editors';
|
||||
import { PublishButton } from './commands-publish-button';
|
||||
import { Runner } from './commands-runner';
|
||||
import { VersionChooser } from './commands-version-chooser';
|
||||
@@ -42,6 +43,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
|
||||
text='Console'
|
||||
onClick={appState.toggleConsole}
|
||||
/>
|
||||
<EditorDropdown appState={appState} />
|
||||
</ControlGroup>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -57,14 +57,10 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
|
||||
if (!this.state.tokenInput) return;
|
||||
this.setState({ verifying: true });
|
||||
|
||||
const octo = await getOctokit();
|
||||
octo.authenticate({
|
||||
type: 'token',
|
||||
token: this.state.tokenInput,
|
||||
});
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
try {
|
||||
const { data } = await octo.users.get({});
|
||||
const { data } = await octo.users.getAuthenticated();
|
||||
this.props.appState.gitHubAvatarUrl = data.avatar_url;
|
||||
this.props.appState.gitHubLogin = data.login;
|
||||
this.props.appState.gitHubName = data.name;
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ContentNames, getContent } from '../content';
|
||||
import { EditorId } from '../../interfaces';
|
||||
import { getContent } from '../content';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface EditorProps {
|
||||
appState: AppState;
|
||||
monaco: typeof MonacoType;
|
||||
monoacoOptions: MonacoType.editor.IEditorOptions;
|
||||
id: string;
|
||||
id: EditorId;
|
||||
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
|
||||
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
|
||||
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
|
||||
@@ -48,11 +49,16 @@ export class Editor extends React.Component<EditorProps> {
|
||||
*
|
||||
* @param {MonacoType.editor.IStandaloneCodeEditor} editor
|
||||
*/
|
||||
public editorDidMount(editor: MonacoType.editor.IStandaloneCodeEditor) {
|
||||
public async editorDidMount(editor: MonacoType.editor.IStandaloneCodeEditor) {
|
||||
const { editorDidMount } = this.props;
|
||||
|
||||
// Set the content on the editor
|
||||
await this.setContent();
|
||||
|
||||
// Set the editor as an available object
|
||||
window.ElectronFiddle.editors[this.props.id] = editor;
|
||||
|
||||
// And notify others
|
||||
if (editorDidMount) {
|
||||
editorDidMount(editor);
|
||||
}
|
||||
@@ -62,8 +68,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
* Initialize Monaco.
|
||||
*/
|
||||
public async initMonaco() {
|
||||
const { monaco, id, appState, monoacoOptions } = this.props;
|
||||
const { version } = appState;
|
||||
const { monaco, monoacoOptions } = this.props;
|
||||
const ref = this.containerRef.current;
|
||||
|
||||
if (ref) {
|
||||
@@ -71,10 +76,11 @@ export class Editor extends React.Component<EditorProps> {
|
||||
language: this.language,
|
||||
theme: 'main',
|
||||
contextmenu: false,
|
||||
value: await getContent(id as ContentNames, version),
|
||||
model: null,
|
||||
...monoacoOptions
|
||||
});
|
||||
this.editorDidMount(this.editor);
|
||||
|
||||
await this.editorDidMount(this.editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +89,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
*/
|
||||
public destroyMonaco() {
|
||||
if (typeof this.editor !== 'undefined') {
|
||||
console.log('Editor: Disposing');
|
||||
this.editor.dispose();
|
||||
}
|
||||
}
|
||||
@@ -90,4 +97,32 @@ export class Editor extends React.Component<EditorProps> {
|
||||
public render() {
|
||||
return <div className='editorContainer' ref={this.containerRef} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content on the editor, including the model and the view state.
|
||||
*
|
||||
* @private
|
||||
* @memberof Editor
|
||||
*/
|
||||
private async setContent() {
|
||||
const { appState, id, monaco } = this.props;
|
||||
const { version } = appState;
|
||||
|
||||
const backup = appState.getAndRemoveEditorValueBackup(id);
|
||||
|
||||
if (backup && backup.model) {
|
||||
console.log(`Editor: Backup found, restoring state`);
|
||||
|
||||
if (backup.viewState) {
|
||||
this.editor.restoreViewState(backup.viewState);
|
||||
}
|
||||
|
||||
this.editor.setModel(backup.model);
|
||||
} else {
|
||||
const value = await getContent(id, version);
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
|
||||
this.editor.setModel(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button, NonIdealState } from '@blueprintjs/core';
|
||||
|
||||
import { EditorId } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export function renderNonIdealState(appState: AppState) {
|
||||
const allEditors = [ EditorId.html, EditorId.main, EditorId.renderer ];
|
||||
const resolveButton = (
|
||||
<Button
|
||||
text='Open all editors'
|
||||
onClick={() => appState.setVisibleMosaics(allEditors)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<NonIdealState
|
||||
action={resolveButton}
|
||||
icon='applications'
|
||||
description='You have closed all editors. You can open them again with the button below or the "Editors" button above!'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { MosaicWindowContext } from 'react-mosaic-component';
|
||||
|
||||
import { DocsDemoPage, MosaicId } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ToolbarButtonProps {
|
||||
appState: AppState;
|
||||
id: MosaicId;
|
||||
}
|
||||
|
||||
export class MaximizeButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
|
||||
this.expand = this.expand.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Button
|
||||
icon='maximize'
|
||||
className='bp3-small'
|
||||
onClick={this.expand}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this panel
|
||||
*/
|
||||
public expand() {
|
||||
const path = this.context.mosaicWindowActions.getPath();
|
||||
return this.context.mosaicActions.expand(path);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
this.remove = this.remove.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Button
|
||||
icon='cross'
|
||||
className='bp3-small'
|
||||
onClick={this.remove}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this panel
|
||||
*/
|
||||
public remove() {
|
||||
this.props.appState.hideAndBackupMosaic(this.props.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class DocsDemoGoHomeButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextTypes = MosaicWindowContext;
|
||||
public context: MosaicWindowContext<MosaicId>;
|
||||
|
||||
constructor(props: ToolbarButtonProps) {
|
||||
super(props);
|
||||
this.goHome = this.goHome.bind(this);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Button
|
||||
icon='home'
|
||||
className='bp3-small'
|
||||
onClick={this.goHome}
|
||||
text='Overview'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this panel
|
||||
*/
|
||||
public goHome() {
|
||||
this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,24 @@
|
||||
import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as React from 'react';
|
||||
import { Mosaic, MosaicWindow } from 'react-mosaic-component';
|
||||
import { Mosaic, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
|
||||
|
||||
import { EditorId } from '../../interfaces';
|
||||
import { EditorId, MosaicId, PanelId } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { updateEditorLayout } from '../../utils/editor-layout';
|
||||
import { getFocusedEditor } from '../../utils/focused-editor';
|
||||
import { getAtPath, setAtPath } from '../../utils/js-path';
|
||||
import { toggleMonaco } from '../../utils/toggle-monaco';
|
||||
import { ContentNames, getContent } from '../content';
|
||||
import { isEditorId } from '../../utils/type-checks';
|
||||
import { getContent } from '../content';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
import { activateTheme } from '../themes';
|
||||
import { Editor } from './editor';
|
||||
|
||||
const options: any = {
|
||||
direction: 'row',
|
||||
first: 'main',
|
||||
second: {
|
||||
direction: 'column',
|
||||
first: 'renderer',
|
||||
second: 'html',
|
||||
},
|
||||
};
|
||||
import { renderNonIdealState } from './editors-non-ideal-state';
|
||||
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
|
||||
import { ShowMe } from './show-me';
|
||||
|
||||
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
minimap: {
|
||||
@@ -34,10 +30,11 @@ const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
const ViewIdMosaic = Mosaic.ofType<EditorId>() as any;
|
||||
const ViewIdMosaicWindow = MosaicWindow.ofType<EditorId>() as any;
|
||||
|
||||
const TITLE_MAP: Record<EditorId, string> = {
|
||||
main: 'Main Process',
|
||||
renderer: 'Renderer Process',
|
||||
html: 'HTML'
|
||||
export const TITLE_MAP: Record<MosaicId, string> = {
|
||||
main: 'Main Process (main.js)',
|
||||
renderer: 'Renderer Process (renderer.js)',
|
||||
html: 'HTML (index.html)',
|
||||
docsDemo: 'Docs & Demos'
|
||||
};
|
||||
|
||||
export interface EditorsProps {
|
||||
@@ -50,13 +47,33 @@ export interface EditorsState {
|
||||
monacoOptions: MonacoType.editor.IEditorOptions;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
// A reaction: Each time mosaicArrangement is changed, we'll update
|
||||
// the editor layout. That method is itself debounced.
|
||||
public disposeLayoutAutorun = reaction(
|
||||
() => this.props.appState.mosaicArrangement,
|
||||
() => updateEditorLayout()
|
||||
);
|
||||
|
||||
constructor(props: EditorsProps) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.renderEditor = this.renderEditor.bind(this);
|
||||
this.renderTile = this.renderTile.bind(this);
|
||||
this.renderGenericPanel = this.renderGenericPanel.bind(this);
|
||||
|
||||
this.state = { monacoOptions: defaultMonacoOptions };
|
||||
|
||||
this.loadMonaco();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed right after the component mounts. We'll setup the IPC listeners here.
|
||||
*
|
||||
* @memberof Editors
|
||||
*/
|
||||
public componentDidMount() {
|
||||
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
|
||||
this.executeCommand(cmd);
|
||||
@@ -69,10 +86,12 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
label: 'Your current fiddle is unsaved. Do you want to discard it?'
|
||||
});
|
||||
|
||||
window.ElectronFiddle.app.setValues({
|
||||
html: await getContent(ContentNames.HTML, version),
|
||||
renderer: await getContent(ContentNames.RENDERER, version),
|
||||
main: await getContent(ContentNames.MAIN, version),
|
||||
window.ElectronFiddle.app.fileManager.setFiddle({
|
||||
values: {
|
||||
html: await getContent(EditorId.html, version),
|
||||
renderer: await getContent(EditorId.renderer, version),
|
||||
main: await getContent(EditorId.main, version),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +102,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
this.setState({ isMounted: true });
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.disposeLayoutAutorun();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to execute a given commandId on the currently focused editor
|
||||
*
|
||||
@@ -135,27 +158,122 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the little tool bar on top of each panel
|
||||
*
|
||||
* @param {MosaicWindowProps<MosaicId>} { title }
|
||||
* @param {MosaicId} id
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
public renderToolbar(
|
||||
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
|
||||
): JSX.Element {
|
||||
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
|
||||
? <DocsDemoGoHomeButton id={id} appState={this.props.appState} />
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Left */}
|
||||
<div>
|
||||
<h5>
|
||||
{title}
|
||||
</h5>
|
||||
</div>
|
||||
{/* Middle */}
|
||||
<div />
|
||||
{/* Right */}
|
||||
<div className='mosaic-controls'>
|
||||
{docsDemoGoHomeMaybe}
|
||||
<MaximizeButton id={id} appState={this.props.appState} />
|
||||
<RemoveButton id={id} appState={this.props.appState} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a Mosaic tile
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} path
|
||||
* @returns {JSX.Element | null}
|
||||
*/
|
||||
public renderTile(id: MosaicId, path: string): JSX.Element | null {
|
||||
const { appState } = this.props;
|
||||
const content = isEditorId(id)
|
||||
? this.renderEditor(id)
|
||||
: this.renderGenericPanel(id, appState);
|
||||
|
||||
return (
|
||||
<ViewIdMosaicWindow
|
||||
className={id}
|
||||
path={path}
|
||||
title={TITLE_MAP[id]}
|
||||
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
|
||||
>
|
||||
{content}
|
||||
</ViewIdMosaicWindow>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a generic panel – so not an editor, but something else
|
||||
*
|
||||
* @param {PanelId} _id
|
||||
* @param {AppState} _appState
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
public renderGenericPanel(_id: PanelId, appState: AppState): JSX.Element {
|
||||
return <ShowMe appState={appState} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an editor
|
||||
*
|
||||
* @param {EditorId} id
|
||||
* @returns {(JSX.Element | null)}
|
||||
* @memberof Editors
|
||||
*/
|
||||
public renderEditor(id: EditorId): JSX.Element | null {
|
||||
const { appState } = this.props;
|
||||
const { monaco } = this.state;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
id={id}
|
||||
monaco={monaco!}
|
||||
appState={appState}
|
||||
monoacoOptions={defaultMonacoOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { monaco } = this.state;
|
||||
|
||||
if (!monaco) return null;
|
||||
|
||||
|
||||
return (
|
||||
<ViewIdMosaic
|
||||
onChange={updateEditorLayout}
|
||||
// tslint:disable-next-line:jsx-no-multiline-js
|
||||
renderTile={(id: any, path: any) => (
|
||||
<ViewIdMosaicWindow className={id} path={path} title={TITLE_MAP[id]} toolbarControls={<div />}>
|
||||
<Editor id={id} monaco={monaco} appState={appState} monoacoOptions={defaultMonacoOptions} />
|
||||
</ViewIdMosaicWindow>
|
||||
)}
|
||||
initialValue={options}
|
||||
onChange={this.onChange}
|
||||
value={appState.mosaicArrangement}
|
||||
zeroStateView={renderNonIdealState(appState)}
|
||||
renderTile={this.renderTile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change in the visible nodes
|
||||
*
|
||||
* @param {(MosaicNode<EditorId> | null)} currentNode
|
||||
*/
|
||||
public onChange(currentNode: MosaicNode<EditorId> | null) {
|
||||
this.props.appState.mosaicArrangement = currentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads monaco. If it's already loaded, it'll just set it on the current state.
|
||||
* We're doing things a bit roundabout to ensure that we're not overloading the
|
||||
@@ -171,7 +289,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
}
|
||||
|
||||
if (!this.state || !this.state.isMounted) {
|
||||
this.state = { monaco, monacoOptions: defaultMonacoOptions };
|
||||
this.state = {
|
||||
monaco,
|
||||
monacoOptions: defaultMonacoOptions
|
||||
};
|
||||
} else {
|
||||
this.setState({ monaco });
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
this.handleDownloadAll = this.handleDownloadAll.bind(this);
|
||||
this.handleDeleteAll = this.handleDeleteAll.bind(this);
|
||||
this.handleChannelChange = this.handleChannelChange.bind(this);
|
||||
this.handleStateChange = this.handleStateChange.bind(this);
|
||||
this.handleDownloadClick = this.handleDownloadClick.bind(this);
|
||||
this.handleAddVersion = this.handleAddVersion.bind(this);
|
||||
|
||||
@@ -54,6 +55,24 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
this.props.appState.updateElectronVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change in which channels should be displayed.
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleStateChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { id, checked } = event.currentTarget;
|
||||
const { appState } = this.props;
|
||||
|
||||
if (!checked) {
|
||||
appState.statesToShow = appState.statesToShow.filter((s) => s !== id);
|
||||
} else {
|
||||
appState.statesToShow.push(id as ElectronVersionState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change in which channels should be displayed.
|
||||
*
|
||||
@@ -120,7 +139,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
return (
|
||||
<div className='settings-electron'>
|
||||
<h2>Electron Settings</h2>
|
||||
{this.renderVersionOptions()}
|
||||
<Callout>
|
||||
{this.renderVersionChannelOptions()}
|
||||
{this.renderVersionStateOptions()}
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
{this.renderAdvancedButtons()}
|
||||
@@ -173,51 +195,91 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
|
||||
/**
|
||||
* Renders the various options for which versions should be displayed
|
||||
* in the small dropdown.
|
||||
*
|
||||
* @private
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
private renderVersionOptions(): JSX.Element {
|
||||
private renderVersionStateOptions(): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const getIsChecked = (state: ElectronVersionState) => {
|
||||
return appState.statesToShow.includes(state);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label='Include Electron versions that are:'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.downloading)}
|
||||
label='Downloading'
|
||||
id='downloading'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.ready)}
|
||||
label='Downloaded'
|
||||
id='ready'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.unknown)}
|
||||
label='Not Downloaded'
|
||||
id='unknown'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the various options for which versions should be displayed
|
||||
*
|
||||
* @private
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
private renderVersionChannelOptions(): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const getIsChecked = (channel: ElectronReleaseChannel) => {
|
||||
return appState.versionsToShow.includes(channel);
|
||||
};
|
||||
|
||||
return (
|
||||
<Callout>
|
||||
<FormGroup
|
||||
label='Include Electron versions from these release channels:'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.stable)}
|
||||
label='Stable'
|
||||
id='Stable'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.beta)}
|
||||
label='Beta'
|
||||
id='Beta'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.nightly)}
|
||||
label='Nightly'
|
||||
id='Nightly'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
|
||||
label='Unsupported'
|
||||
id='Unsupported'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
<FormGroup
|
||||
label='Include Electron versions from these release channels:'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.stable)}
|
||||
label='Stable'
|
||||
id='Stable'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.beta)}
|
||||
label='Beta'
|
||||
id='Beta'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.nightly)}
|
||||
label='Nightly'
|
||||
id='Nightly'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
|
||||
label='Unsupported'
|
||||
id='Unsupported'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,7 +316,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
* @returns {Array<JSX.Element>}
|
||||
*/
|
||||
private renderTableRows(): Array<JSX.Element | null> {
|
||||
const { versions, versionsToShow } = this.props.appState;
|
||||
const { versions, versionsToShow, statesToShow } = this.props.appState;
|
||||
|
||||
return sortedElectronMap<JSX.Element | null>(versions, (key, item) => {
|
||||
// Check if we want to show the version
|
||||
@@ -262,6 +324,11 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we want to show the state
|
||||
if (!statesToShow.includes(item.state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={item.version}>
|
||||
<td>{item.version}</td>
|
||||
@@ -273,7 +340,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable state indicator for an Electorn version
|
||||
* Returns a human-readable state indicator for an Electron version
|
||||
*
|
||||
* @param {ElectronVersion} item
|
||||
* @returns {JSX.Element}
|
||||
@@ -334,6 +401,6 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
buttonProps.onClick = () => appState.downloadVersion(key);
|
||||
}
|
||||
|
||||
return <Button {...buttonProps} />;
|
||||
return <Button {...buttonProps} type={undefined} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
super(props);
|
||||
|
||||
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
|
||||
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,14 +36,30 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
this.props.appState.isKeepingUserDataDirs = checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change on whether or not electron should log more things
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleElectronLoggingChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { checked } = event.currentTarget;
|
||||
this.props.appState.isEnablingElectronLogging = checked;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isKeepingUserDataDirs } = this.props.appState;
|
||||
const { isKeepingUserDataDirs, isEnablingElectronLogging } = this.props.appState;
|
||||
|
||||
const deleteUserDirLabel = `
|
||||
Whenever Electron runs, it creates a user data directory for cookies, the cache,
|
||||
and various other things that it needs to keep around. Since fiddles are usually
|
||||
just run once, we delete this directory after your fiddle exits. Enable this
|
||||
setting to keep the user data directories around.
|
||||
`.trim();
|
||||
const electronLoggingLabel = `
|
||||
There are some flags that Electron uses to log extra information both internally
|
||||
and through Chromium. Enable this option to make Fiddle produce those logs.`.trim();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -52,14 +69,24 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup label={deleteUserDirLabel}>
|
||||
<Checkbox
|
||||
checked={isKeepingUserDataDirs}
|
||||
label='Do not delete user data directories.'
|
||||
onChange={this.handleDeleteDataChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
<FormGroup label={deleteUserDirLabel}>
|
||||
<Checkbox
|
||||
checked={isKeepingUserDataDirs}
|
||||
label='Do not delete user data directories.'
|
||||
onChange={this.handleDeleteDataChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup label={electronLoggingLabel}>
|
||||
<Checkbox
|
||||
checked={isEnablingElectronLogging}
|
||||
label='Enable advanced Electron logging.'
|
||||
onChange={this.handleElectronLoggingChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { DOCS_DEMO_COMPONENTS } from './show-me/index';
|
||||
|
||||
export interface ShowMeProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
/**3
|
||||
* The root component for "show me" content - the fourth helpful
|
||||
* panel.
|
||||
*
|
||||
* @class ShowMe
|
||||
* @extends {React.Component<ShowMeProps, ShowMeState>}
|
||||
*/
|
||||
@observer
|
||||
export class ShowMe extends React.Component<ShowMeProps, {}> {
|
||||
public render() {
|
||||
const { currentDocsDemoPage: showMeName } = this.props.appState;
|
||||
const Content = DOCS_DEMO_COMPONENTS[showMeName] || DOCS_DEMO_COMPONENTS.DEFAULT;
|
||||
|
||||
return (
|
||||
<div className='show-me-panel'>
|
||||
<Content appState={this.props.appState} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button, Callout, Icon } from '@blueprintjs/core';
|
||||
import { remote } from 'electron';
|
||||
|
||||
import { renderMoreDocumentation } from './more-information';
|
||||
import { getSubsetOnly } from './subset-only';
|
||||
|
||||
/**
|
||||
* Show and hide button examples, only rendered on macOS
|
||||
*
|
||||
* @returns {JSX.Element | null}
|
||||
*/
|
||||
function ShowHide() {
|
||||
if (process.platform !== 'darwin') return null;
|
||||
|
||||
const onClick = () => {
|
||||
remote.app.hide();
|
||||
setTimeout(() => {
|
||||
remote.app.show();
|
||||
remote.app.focus();
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>This button hides Electron Fiddle right away, showing it again in two seconds.</p>
|
||||
<Button
|
||||
id='show-hide'
|
||||
icon='eye-off'
|
||||
text='Hide Electron Fiddle'
|
||||
onClick={onClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export function ShowMeApp(_props: any): JSX.Element {
|
||||
const [ secondsLeft, setSeconds ] = React.useState(-1);
|
||||
|
||||
const playFocus = () => {
|
||||
setSeconds(3);
|
||||
setTimeout(() => setSeconds(2), 1000);
|
||||
setTimeout(() => setSeconds(1), 2000);
|
||||
setTimeout(() => {
|
||||
remote.app.focus();
|
||||
setSeconds(-1);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const [ paths, setPaths ] = React.useState('');
|
||||
const playPaths = () => {
|
||||
const pathsToQuery = [
|
||||
'home',
|
||||
'appData',
|
||||
'userData',
|
||||
'temp',
|
||||
'downloads',
|
||||
'desktop'
|
||||
];
|
||||
|
||||
let result = '';
|
||||
pathsToQuery.forEach((item) => {
|
||||
result += `${item}: ${remote.app.getPath(item)}\n`;
|
||||
});
|
||||
|
||||
setPaths(result);
|
||||
};
|
||||
|
||||
const [ metrics, setMetrics ] = React.useState('');
|
||||
const playMetrics = () => {
|
||||
setMetrics(JSON.stringify(remote.app.getAppMetrics(), undefined, 2));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
|
||||
<p className='bp3r-running-text'>
|
||||
The <code>app</code> module controls the app's application life-cycle. Most of the
|
||||
events and methods available on this module are responsible for handling how your
|
||||
interacts with the operating system or to set application-wide settings.
|
||||
</p>
|
||||
<h3>API Demos</h3>
|
||||
{getSubsetOnly('app')}
|
||||
<Callout
|
||||
title='Hiding, Showing, Focussing'
|
||||
icon='eye-open'
|
||||
>
|
||||
<p>
|
||||
The app can ask the operating system for window focus. On macOS, it can additionally
|
||||
request that the app be hidden or shown. Give it a try: Click on the button below,
|
||||
focus another app, and wait for two seconds to see Electron Fiddle become the focused
|
||||
app again.
|
||||
</p>
|
||||
<Button
|
||||
id='focus'
|
||||
icon='lightbulb'
|
||||
text={`Focus Electron Fiddle${secondsLeft > 0 ? ` in ${secondsLeft}s` : ''}`}
|
||||
onClick={playFocus}
|
||||
/>
|
||||
<ShowHide />
|
||||
</Callout>
|
||||
<Callout
|
||||
title='Paths'
|
||||
icon='folder-open'
|
||||
>
|
||||
<p>
|
||||
Need to query information about various paths in a cross-platform manner? Electron
|
||||
can help. The button queries the operating system for some of them.
|
||||
</p>
|
||||
<Button
|
||||
id='special-paths'
|
||||
icon='play'
|
||||
text={`Get special directory paths`}
|
||||
onClick={playPaths}
|
||||
/>
|
||||
<pre id='special-paths-content'>
|
||||
{paths}
|
||||
</pre>
|
||||
</Callout>
|
||||
<Callout
|
||||
title='Process & Device Information'
|
||||
icon='pulse'
|
||||
>
|
||||
<p>
|
||||
Need to query information about the process or system? The <code>app</code> module lets
|
||||
developers query for information about the running app, hardware, and operating system.
|
||||
A good example are process metrics.
|
||||
</p>
|
||||
<Button
|
||||
id='process-metrics'
|
||||
icon='play'
|
||||
text={`Get process metrics`}
|
||||
onClick={playMetrics}
|
||||
/>
|
||||
<pre id='process-metrics-content'>
|
||||
{metrics}
|
||||
</pre>
|
||||
</Callout>
|
||||
{renderMoreDocumentation()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Button, Icon } from '@blueprintjs/core';
|
||||
import * as React from 'react';
|
||||
|
||||
import { DocsDemoPage } from '../../../interfaces';
|
||||
import { AppState } from '../../state';
|
||||
import { DOCS_DEMO_COMPONENTS, DOCS_DEMO_NAMES } from './index';
|
||||
import { renderMoreDocumentation } from './more-information';
|
||||
|
||||
export function ShowMeDefault(props: { appState: AppState }): JSX.Element {
|
||||
const showMeMenu: Array<JSX.Element> = Object.keys(DOCS_DEMO_COMPONENTS)
|
||||
.filter((key) => key !== DocsDemoPage.DEFAULT)
|
||||
.map((key) => {
|
||||
return (
|
||||
<li key={key}>
|
||||
<Button
|
||||
minimal={true}
|
||||
icon='play'
|
||||
text={DOCS_DEMO_NAMES[key]}
|
||||
onClick={() => (props.appState.currentDocsDemoPage = key as DocsDemoPage)}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
|
||||
<p className='bp3r-running-text'>
|
||||
This panel offers useful information about Electron APIs – and
|
||||
easy way to try some of the methods it offers. Fiddle has an example fiddle
|
||||
for every module available in Electron.
|
||||
</p>
|
||||
<p>
|
||||
Clicking on any of the modules below will open up a fiddle showcasing
|
||||
that particular module.
|
||||
</p>
|
||||
<ul className='show-me-list'>
|
||||
{...showMeMenu}
|
||||
</ul>
|
||||
{renderMoreDocumentation()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { DocsDemoPage } from '../../../interfaces';
|
||||
|
||||
import { ShowMeApp } from './app';
|
||||
import { ShowMeDefault } from './default';
|
||||
|
||||
export const DOCS_DEMO_COMPONENTS: Record<DocsDemoPage, any> = {
|
||||
DEFAULT: ShowMeDefault,
|
||||
DEMO_APP: ShowMeApp
|
||||
};
|
||||
|
||||
export const DOCS_DEMO_NAMES: Record<DocsDemoPage, string> = {
|
||||
DEFAULT: 'Home',
|
||||
DEMO_APP: 'App Demos'
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { shell } from 'electron';
|
||||
|
||||
/**
|
||||
* Renders a "more documentation" block, typically used at the end of a "show me" documentation piece.
|
||||
*
|
||||
* @export
|
||||
* @param {string} [url='electronjs.org/docs']
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function renderMoreDocumentation(
|
||||
url: string = 'electronjs.org/docs'
|
||||
): JSX.Element {
|
||||
const fullUrl = `https://${url}`;
|
||||
const gitHubUrl = `https://github.com/electron/fiddle`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
<p className='b3-running-text'>
|
||||
For more documentation, visit <a id='open-url' onClick={() => shell.openExternal(fullUrl)}>{url}</a>, where
|
||||
you can find the full documentation for Electron.
|
||||
</p>
|
||||
<p className='bp3-text-muted'>
|
||||
By the way, Electron Fiddle and the documentation you see here is entirely open source. If you
|
||||
have ideas on how to improve it, we'd love to have your contributions! You can find the
|
||||
repository on <a id='open-github' onClick={() => shell.openExternal(gitHubUrl)}>GitHub</a>.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { shell } from 'electron';
|
||||
|
||||
import { getDocsUrlForModule } from '../../../utils/docs-urls';
|
||||
|
||||
/**
|
||||
* Returns a common string we use as a disclaimer to explain that this set of demos
|
||||
* is only meant to be a short demonstration, not displaying all of the modules
|
||||
* abilities.
|
||||
*
|
||||
* @export
|
||||
* @param {string} moduleName
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function getSubsetOnly(moduleName: string): JSX.Element {
|
||||
const { full, short } = getDocsUrlForModule(moduleName);
|
||||
|
||||
return (
|
||||
<p className='bp3-running-text'>
|
||||
The following demos display only a subset of what the <code>{moduleName}</code>
|
||||
module is capable of. If you want to see its full abilities, check out the
|
||||
documentation on <a id='open-url' onClick={() => shell.openExternal(full)}>{short}</a>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,20 @@
|
||||
import { remote } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import { EditorId, MosaicId } from '../interfaces';
|
||||
|
||||
// Reminder: When testing, this file is mocked in tests/setup.js
|
||||
|
||||
export const USER_DATA_PATH = remote.app.getPath('userData');
|
||||
export const CONFIG_PATH = path.join(remote.app.getPath('home'), '.electron-fiddle');
|
||||
|
||||
export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
direction: 'row',
|
||||
first: EditorId.main,
|
||||
second: {
|
||||
direction: 'column',
|
||||
first: EditorId.renderer,
|
||||
second: EditorId.html,
|
||||
}
|
||||
};
|
||||
|
||||
+12
-16
@@ -1,30 +1,26 @@
|
||||
export const enum ContentNames {
|
||||
HTML = 'html',
|
||||
RENDERER = 'renderer',
|
||||
MAIN = 'main'
|
||||
}
|
||||
import { EditorId } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Returns expected content for a given name.
|
||||
*
|
||||
* @export
|
||||
* @param {ContentNames} name
|
||||
* @param {EditorId} name
|
||||
* @param {string} [version]
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function getContent(
|
||||
name: ContentNames,
|
||||
version?: string
|
||||
name: EditorId,
|
||||
version?: string,
|
||||
): Promise<string> {
|
||||
if (name === ContentNames.HTML) {
|
||||
if (name === EditorId.html) {
|
||||
return (await import('../content/html')).html;
|
||||
}
|
||||
|
||||
if (name === ContentNames.RENDERER) {
|
||||
if (name === EditorId.renderer) {
|
||||
return (await import('../content/renderer')).renderer;
|
||||
}
|
||||
|
||||
if (name === ContentNames.MAIN) {
|
||||
if (name === EditorId.main) {
|
||||
// We currently only distinguish between loadFile
|
||||
// and loadURL. Todo: Properly version the quick-start.
|
||||
if (version && version.startsWith('1.')) {
|
||||
@@ -40,18 +36,18 @@ export async function getContent(
|
||||
/**
|
||||
* Did the content change?
|
||||
*
|
||||
* @param {ContentNames} name
|
||||
* @param {EditorId} name
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export async function isContentUnchanged(name: ContentNames): Promise<boolean> {
|
||||
export async function isContentUnchanged(name: EditorId): Promise<boolean> {
|
||||
if (!window.ElectronFiddle || !window.ElectronFiddle.app) return false;
|
||||
|
||||
const values = await window.ElectronFiddle.app.getValues({ include: false });
|
||||
|
||||
// Handle main case, which needs to check both possible versions
|
||||
if (name === ContentNames.MAIN) {
|
||||
const isChanged1x = await getContent(ContentNames.MAIN, '1.0') === values.main;
|
||||
const isChangedOther = await getContent(ContentNames.MAIN) === values.main;
|
||||
if (name === EditorId.main) {
|
||||
const isChanged1x = await getContent(EditorId.main, '1.0') === values.main;
|
||||
const isChangedOther = await getContent(EditorId.main) === values.main;
|
||||
|
||||
return isChanged1x || isChangedOther;
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { EditorValues, Files, FileTransform } from '../interfaces';
|
||||
import { EditorValues, Files, FileTransform, SetFiddleOptions } from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
|
||||
@@ -43,7 +43,8 @@ export class FileManager {
|
||||
*/
|
||||
public async openTemplate(name: string) {
|
||||
const values = await getTemplateValues(name);
|
||||
this.setFiddle(values);
|
||||
|
||||
this.setFiddle({ values, templateName: name });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,19 +66,20 @@ export class FileManager {
|
||||
renderer: await this.readFile(path.join(filePath, RENDERER_JS_NAME)),
|
||||
};
|
||||
|
||||
this.setFiddle(values);
|
||||
this.setFiddle({ values, filePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Got values? This method will load them.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {SetFiddleOptions} options
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async setFiddle(values: EditorValues, filePath?: string) {
|
||||
public async setFiddle({ values, filePath, templateName }: SetFiddleOptions) {
|
||||
this.appState.gistId = '';
|
||||
this.appState.isMyGist = false;
|
||||
this.appState.localPath = filePath || null;
|
||||
this.appState.localPath = filePath;
|
||||
this.appState.templateName = templateName;
|
||||
|
||||
this.appState.setWarningDialogTexts({
|
||||
label: `Opening this fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
|
||||
+13
-1
@@ -181,7 +181,19 @@ export class Runner {
|
||||
const binaryPath = binaryManager.getElectronBinaryPath(version, localPath);
|
||||
console.log(`Runner: Binary ${binaryPath} ready, launching`);
|
||||
|
||||
this.child = spawn(binaryPath, [ dir, '--inspect' ]);
|
||||
const env = { ...process.env };
|
||||
if (this.appState.isEnablingElectronLogging) {
|
||||
env.ELECTRON_ENABLE_LOGGING = 'true';
|
||||
env.ELECTRON_ENABLE_STACK_DUMPING = 'true';
|
||||
} else {
|
||||
delete env.ELECTRON_ENABLE_LOGGING;
|
||||
delete env.ELECTRON_ENABLE_STACK_DUMPING;
|
||||
}
|
||||
|
||||
this.child = spawn(binaryPath, [ dir, '--inspect' ], {
|
||||
cwd: dir,
|
||||
env,
|
||||
});
|
||||
this.appState.isRunning = true;
|
||||
pushOutput(`Electron v${version} started.`);
|
||||
|
||||
|
||||
+112
-5
@@ -1,9 +1,14 @@
|
||||
import { action, autorun, computed, observable, when } from 'mobx';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import {
|
||||
ALL_MOSAICS,
|
||||
DocsDemoPage,
|
||||
EditorId,
|
||||
ElectronVersion,
|
||||
ElectronVersionSource,
|
||||
ElectronVersionState,
|
||||
MosaicId,
|
||||
NpmVersion,
|
||||
OutputEntry,
|
||||
OutputOptions,
|
||||
@@ -11,10 +16,14 @@ import {
|
||||
} from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { arrayToStringMap } from '../utils/array-to-stringmap';
|
||||
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
|
||||
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
|
||||
import { getName } from '../utils/get-title';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
|
||||
import { BinaryManager } from './binary';
|
||||
import { ContentNames, getContent, isContentUnchanged } from './content';
|
||||
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
|
||||
import { getContent, isContentUnchanged } from './content';
|
||||
import { updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { activateTheme } from './themes';
|
||||
@@ -62,7 +71,11 @@ export class AppState {
|
||||
@observable public versionsToShow: Array<ElectronReleaseChannel> =
|
||||
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|
||||
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
|
||||
@observable public statesToShow: Array<ElectronVersionState> =
|
||||
this.retrieve('statesToShow') as Array<ElectronVersionState>
|
||||
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
|
||||
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
|
||||
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
|
||||
|
||||
@observable public binaryManager: BinaryManager = new BinaryManager();
|
||||
|
||||
@@ -71,11 +84,14 @@ export class AppState {
|
||||
@observable public isMyGist: boolean = false;
|
||||
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
|
||||
@observable public output: Array<OutputEntry> = [];
|
||||
@observable public localPath: string | null = null;
|
||||
@observable public localPath: string | undefined;
|
||||
@observable public isUpdatingElectronVersions = false;
|
||||
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
|
||||
@observable public warningDialogLastResult: boolean | null = null;
|
||||
@observable public isRunning = false;
|
||||
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
|
||||
@observable public templateName: string | undefined;
|
||||
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
|
||||
// -- Various "isShowing" settings ------------------
|
||||
@observable public isConsoleShowing: boolean = false;
|
||||
@@ -86,6 +102,11 @@ export class AppState {
|
||||
@observable public isAddVersionDialogShowing: boolean = false;
|
||||
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
|
||||
|
||||
// -- Editor Values stored when we close the editor ------------------
|
||||
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = {
|
||||
docsDemo: true // Closed by default
|
||||
};
|
||||
|
||||
private outputBuffer: string = '';
|
||||
private name: string;
|
||||
|
||||
@@ -95,6 +116,7 @@ export class AppState {
|
||||
this.pushError = this.pushError.bind(this);
|
||||
this.pushOutput = this.pushOutput.bind(this);
|
||||
this.removeVersion = this.removeVersion.bind(this);
|
||||
this.setToken = this.setToken.bind(this);
|
||||
this.setVersion = this.setVersion.bind(this);
|
||||
this.showTour = this.showTour.bind(this);
|
||||
this.signOutGitHub = this.signOutGitHub.bind(this);
|
||||
@@ -105,6 +127,7 @@ export class AppState {
|
||||
|
||||
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
|
||||
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
|
||||
ipcRendererManager.on(IpcEvents.SET_GITHUB_TOKEN, this.setToken);
|
||||
|
||||
// Setup auto-runs
|
||||
autorun(() => this.save('theme', this.theme));
|
||||
@@ -114,8 +137,10 @@ export class AppState {
|
||||
autorun(() => this.save('gitHubToken', this.gitHubToken));
|
||||
autorun(() => this.save('gitHubPublishAsPublic', this.gitHubPublishAsPublic));
|
||||
autorun(() => this.save('isKeepingUserDataDirs', this.isKeepingUserDataDirs));
|
||||
autorun(() => this.save('isEnablingElectronLogging', this.isEnablingElectronLogging));
|
||||
autorun(() => this.save('version', this.version));
|
||||
autorun(() => this.save('versionsToShow', this.versionsToShow));
|
||||
autorun(() => this.save('statesToShow', this.statesToShow));
|
||||
|
||||
autorun(() => {
|
||||
if (this.isUnsaved) {
|
||||
@@ -132,7 +157,16 @@ export class AppState {
|
||||
// The user confirmed, let's close for real.
|
||||
if (this.warningDialogLastResult) {
|
||||
window.onbeforeunload = null;
|
||||
window.close();
|
||||
|
||||
// Should we just close or quit?
|
||||
const remote = require('electron').remote;
|
||||
const isQuitting = remote.getGlobal('isQuitting');
|
||||
|
||||
if (isQuitting) {
|
||||
remote.app.quit();
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -229,6 +263,19 @@ export class AppState {
|
||||
this.resetView({ isTourShowing: true });
|
||||
}
|
||||
|
||||
@action public async setToken(_: any, token: string) {
|
||||
this.setWarningDialogTexts({
|
||||
label: 'Do you want to sign in to Electron Fiddle using this token?',
|
||||
ok: 'Yes',
|
||||
cancel: 'No'
|
||||
});
|
||||
this.isWarningDialogShowing = true;
|
||||
await when(() => !this.isWarningDialogShowing);
|
||||
if (this.warningDialogLastResult) {
|
||||
this.gitHubToken = token;
|
||||
}
|
||||
}
|
||||
|
||||
@action public setTheme(fileName?: string) {
|
||||
this.theme = fileName || '';
|
||||
activateTheme(undefined, undefined, fileName);
|
||||
@@ -339,8 +386,8 @@ export class AppState {
|
||||
this.version = version;
|
||||
|
||||
// Should we update the editor?
|
||||
if (await isContentUnchanged(ContentNames.MAIN)) {
|
||||
const main = await getContent(ContentNames.MAIN, version);
|
||||
if (await isContentUnchanged(EditorId.main)) {
|
||||
const main = await getContent(EditorId.main, version);
|
||||
window.ElectronFiddle.app.setValues({ main }, false);
|
||||
}
|
||||
|
||||
@@ -435,6 +482,66 @@ export class AppState {
|
||||
console.warn(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editor value for a given editor. Deletes the value after
|
||||
* accessing it.
|
||||
*
|
||||
* @param {EditorId} id
|
||||
*/
|
||||
@action public getAndRemoveEditorValueBackup(id: EditorId): EditorBackup | null {
|
||||
const value = this.closedPanels[id];
|
||||
|
||||
if (isEditorBackup(value)) {
|
||||
delete this.closedPanels[id];
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@action public setVisibleMosaics(visible: Array<MosaicId>) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
|
||||
for (const id of ALL_MOSAICS) {
|
||||
if (!visible.includes(id) && currentlyVisible.includes(id)) {
|
||||
this.closedPanels[id] = isEditorId(id)
|
||||
? getEditorBackup(id)
|
||||
: true;
|
||||
}
|
||||
|
||||
// Remove the backup for panels now. Editors will remove their
|
||||
// backup once the data has been loaded.
|
||||
if (isPanelId(id) && visible.includes(id) && !currentlyVisible.includes(id)) {
|
||||
delete this.closedPanels[id];
|
||||
}
|
||||
}
|
||||
|
||||
const updatedArrangement = createMosaicArrangement(visible);
|
||||
console.log(`State: Setting visible mosaic panels`, visible, updatedArrangement);
|
||||
|
||||
this.mosaicArrangement = updatedArrangement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the panel for a given MosaicId.
|
||||
*
|
||||
* @param {MosaicId} id
|
||||
*/
|
||||
@action public hideAndBackupMosaic(id: MosaicId) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
this.setVisibleMosaics(currentlyVisible.filter((v) => v !== id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the editor value for a given editor.
|
||||
*
|
||||
* @param {MosaicId} id
|
||||
*/
|
||||
@action public showMosaic(id: MosaicId) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
this.setVisibleMosaics([ ...currentlyVisible, id ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the view, optionally with certain view flags enabled.
|
||||
*
|
||||
|
||||
@@ -15,8 +15,8 @@ import { fancyImport } from '../utils/import';
|
||||
export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
const path = await fancyImport<typeof pathType>('path');
|
||||
const fs = await fancyImport<typeof fsExtraType>('fs-extra');
|
||||
|
||||
const templatePath = path.join(__dirname, '../../static/templates', name.toLowerCase());
|
||||
const templatesPath = path.join(__dirname, '../../static/show-me');
|
||||
const templatePath = path.join(templatesPath, name.toLowerCase());
|
||||
|
||||
const getFile = async (fileName: string) => {
|
||||
try {
|
||||
@@ -25,7 +25,15 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.warn(`Could not get template file:`, error);
|
||||
console.warn(`getTemplateValues(): Could not get template file:`, error);
|
||||
|
||||
if (fs.existsSync(templatesPath)) {
|
||||
const contents = fs.readdirSync(templatesPath);
|
||||
console.log(`getTemplateValues(): ${templatesPath} contents:`, contents);
|
||||
} else {
|
||||
console.log(`getTemplateValues(): ${templatesPath} does not exist`);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import {
|
||||
remote,
|
||||
TouchBarButtonConstructorOptions,
|
||||
TouchBarScrubberConstructorOptions
|
||||
} from 'electron';
|
||||
import { autorun } from 'mobx';
|
||||
|
||||
import { ElectronVersion, ElectronVersionState } from '../interfaces';
|
||||
import { getNiceGreeting } from '../utils/nice-greeting';
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import { AppState } from './state';
|
||||
|
||||
const { TouchBar } = remote;
|
||||
const {
|
||||
TouchBarButton,
|
||||
TouchBarScrubber,
|
||||
TouchBarSpacer,
|
||||
TouchBarLabel
|
||||
} = TouchBar;
|
||||
|
||||
/**
|
||||
* Helper method: Returns an icon (emoji, lol) for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {Partial<ElectronVersion>} { state }
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getItemIcon(
|
||||
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
|
||||
) {
|
||||
return state === 'ready'
|
||||
? '💾'
|
||||
: state === 'downloading' ? '⏬' : '☁';
|
||||
}
|
||||
|
||||
export class TouchBarManager {
|
||||
public browserWindow: Electron.BrowserWindow;
|
||||
public consoleBtn: Electron.TouchBarButton;
|
||||
public startStopBtn: Electron.TouchBarButton;
|
||||
public touchBar: Electron.TouchBar;
|
||||
public versionSelector: Electron.TouchBarScrubber;
|
||||
public versionSelectorBtn: Electron.TouchBarButton;
|
||||
public selectedVersion: string = '';
|
||||
public versionSelectorSelectBtn: Electron.TouchBarButton;
|
||||
// Lol have mercy, TS
|
||||
public items: Array<Electron.TouchBarButton
|
||||
| Electron.TouchBarColorPicker
|
||||
| Electron.TouchBarGroup
|
||||
| Electron.TouchBarLabel
|
||||
| Electron.TouchBarPopover
|
||||
| Electron.TouchBarScrubber
|
||||
| Electron.TouchBarSegmentedControl
|
||||
| Electron.TouchBarSlider
|
||||
| Electron.TouchBarSpacer>
|
||||
| undefined;
|
||||
|
||||
constructor(public readonly appState: AppState) {
|
||||
this.selectVersion = this.selectVersion.bind(this);
|
||||
this.updateStartStopBtn = this.updateStartStopBtn.bind(this);
|
||||
this.updateVersionSelectorItems = this.updateVersionSelectorItems.bind(this);
|
||||
this.updateVersionButton = this.updateVersionButton.bind(this);
|
||||
|
||||
this.startStopBtn = new TouchBarButton(this.getStartStopButtonOptions());
|
||||
this.consoleBtn = new TouchBarButton(this.getConsoleButtonOptions());
|
||||
this.versionSelectorBtn = new TouchBarButton(this.getVersionButtonOptions());
|
||||
this.versionSelectorSelectBtn = new TouchBarButton(this.getVersionSelectorSelectButtonOptions());
|
||||
this.versionSelector = new TouchBarScrubber(this.getVersionSelectorOptions());
|
||||
|
||||
this.setupTouchBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called right after we instantiate the manager. It sets up
|
||||
* autoruns that respond to state changes (like new versions) and also
|
||||
* greets the user with a kind little message.
|
||||
*/
|
||||
public setupTouchBar() {
|
||||
// First, the welcome
|
||||
this.setWelcomeItems();
|
||||
|
||||
// Then, go with the intro
|
||||
setTimeout(() => {
|
||||
this.setDefaultItems();
|
||||
}, 4000);
|
||||
|
||||
autorun(this.updateStartStopBtn);
|
||||
autorun(this.updateVersionSelectorItems);
|
||||
autorun(this.updateVersionButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever run/stop status changes, we'll update it (thanks, mobx)
|
||||
*/
|
||||
public updateStartStopBtn() {
|
||||
const { label } = this.getStartStopButtonOptions();
|
||||
this.startStopBtn.label = label!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever the electron versions change, we'll update
|
||||
* the items in the scrubber (thanks, mobx)
|
||||
*/
|
||||
public updateVersionSelectorItems() {
|
||||
this.versionSelector.items = this.getVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever the Electron version changes, update the version button
|
||||
*/
|
||||
public updateVersionButton() {
|
||||
const { label } = this.getVersionButtonOptions();
|
||||
this.versionSelectorBtn.label = label!;
|
||||
}
|
||||
|
||||
public getVersions() {
|
||||
return sortedElectronMap(this.appState.versions || {}, (key, version) => ({
|
||||
label: `${getItemIcon(version)} ${key}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely set the touch bar on the current window
|
||||
*
|
||||
* @param {Partial<Electron.TouchBarConstructorOptions>} options
|
||||
*/
|
||||
public setTouchBar(options: Partial<Electron.TouchBarConstructorOptions>) {
|
||||
try {
|
||||
this.items = options.items;
|
||||
this.touchBar = new TouchBar(options as Electron.TouchBarConstructorOptions);
|
||||
this.browserWindow = this.browserWindow || remote.getCurrentWindow();
|
||||
this.browserWindow.setTouchBar(this.touchBar);
|
||||
} catch (error) {
|
||||
console.warn(`Could not set touch bar`, { error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bar that just contains a little welcome message
|
||||
*/
|
||||
public setWelcomeItems() {
|
||||
this.setTouchBar({
|
||||
items: [
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
new TouchBarLabel({ label: getNiceGreeting() }),
|
||||
new TouchBarSpacer({ size: 'flexible' })
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A bar with the default items
|
||||
*/
|
||||
public setDefaultItems() {
|
||||
this.setTouchBar({
|
||||
items: [
|
||||
this.startStopBtn,
|
||||
this.consoleBtn,
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
this.versionSelectorBtn,
|
||||
new TouchBarSpacer({ size: 'flexible' })
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A bar that just contains the version scrubber
|
||||
*/
|
||||
public setVersionSelectorItems() {
|
||||
this.setTouchBar({
|
||||
items: [
|
||||
this.versionSelector,
|
||||
this.versionSelectorSelectBtn,
|
||||
],
|
||||
escapeItem: new TouchBarButton(
|
||||
this.getVersionSelectorEscButtonOptions()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The version selector gets a custom "escape" button that'll
|
||||
* take the user back to the "default" touch bar interface
|
||||
*/
|
||||
public getVersionSelectorEscButtonOptions() {
|
||||
return {
|
||||
label: `Back`,
|
||||
click: () => {
|
||||
this.setDefaultItems();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The version selector also gets a "select" button that'll
|
||||
* commit the selected version
|
||||
*/
|
||||
public getVersionSelectorSelectButtonOptions() {
|
||||
return {
|
||||
label: `🎯 Select`,
|
||||
click: () => {
|
||||
if (this.selectedVersion) {
|
||||
this.appState.setVersion(this.selectedVersion);
|
||||
}
|
||||
|
||||
this.setDefaultItems();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The start button is wired up to launching the current
|
||||
* fiddle.
|
||||
*
|
||||
* @returns {TouchBarButtonConstructorOptions}
|
||||
*/
|
||||
public getStartStopButtonOptions(): TouchBarButtonConstructorOptions {
|
||||
const label = this.appState.isRunning
|
||||
? '🛑 Stop'
|
||||
: '🚀 Run';
|
||||
|
||||
const click = () => this.appState.isRunning
|
||||
? window.ElectronFiddle.app.runner.stop()
|
||||
: window.ElectronFiddle.app.runner.run();
|
||||
|
||||
return { label, click };
|
||||
}
|
||||
|
||||
/**
|
||||
* The console button is wired up to toggle the console
|
||||
*
|
||||
* @returns {TouchBarButtonConstructorOptions}
|
||||
*/
|
||||
public getConsoleButtonOptions(): TouchBarButtonConstructorOptions {
|
||||
const label = '🖥 Console';
|
||||
const click = this.appState.toggleConsole;
|
||||
|
||||
return { label, click };
|
||||
}
|
||||
|
||||
/**
|
||||
* The version button opens the scrubber, allowing selection
|
||||
* of a version
|
||||
*
|
||||
* @returns {TouchBarButtonConstructorOptions}
|
||||
*/
|
||||
public getVersionButtonOptions(): TouchBarButtonConstructorOptions {
|
||||
const label = `🔖 Electron ${this.appState.version}`;
|
||||
const click = () => this.setVersionSelectorItems();
|
||||
|
||||
return { label, click };
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the version selection scrubber
|
||||
*
|
||||
* @returns {TouchBarScrubberConstructorOptions}
|
||||
*/
|
||||
public getVersionSelectorOptions(
|
||||
): TouchBarScrubberConstructorOptions {
|
||||
return {
|
||||
select: this.selectVersion,
|
||||
highlight: this.selectVersion,
|
||||
items: [],
|
||||
selectedStyle: 'background',
|
||||
overlayStyle: 'outline',
|
||||
mode: 'free',
|
||||
continuous: true,
|
||||
showArrowButtons: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once the version selection scrubber is closed (
|
||||
* with confirmation)
|
||||
*
|
||||
* @param {number} index
|
||||
*/
|
||||
public selectVersion(index: number) {
|
||||
const versions = sortedElectronMap(this.appState.versions || {}, (k) => k);
|
||||
|
||||
this.selectedVersion = versions[index];
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ export async function forgeTransform(files: Files): Promise<Files> {
|
||||
|
||||
// devDependencies
|
||||
parsed.devDependencies = parsed.devDependencies || {};
|
||||
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.22';
|
||||
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.22';
|
||||
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.22';
|
||||
parsed.devDependencies['@electron-forge/maker-squirrel'] = '6.0.0-beta.22';
|
||||
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.22';
|
||||
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-squirrel'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.34';
|
||||
|
||||
// Scripts
|
||||
parsed.scripts = parsed.scripts || {};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Get all the documentation urls for a given module name.
|
||||
*
|
||||
* @export
|
||||
* @param {string} moduleName
|
||||
*/
|
||||
export function getDocsUrlForModule(moduleName: string) {
|
||||
return {
|
||||
full: `https://electronjs.org/docs/api/${moduleName}`,
|
||||
repo: `https://github.com/electron/electron/blob/master/docs/api/${moduleName}.md`,
|
||||
short: `electronjs.org/docs/api/${moduleName}`
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { editor } from 'monaco-editor';
|
||||
|
||||
import { EditorId } from '../interfaces';
|
||||
import { getEditorModel } from './editor-model';
|
||||
import { getEditorValue } from './editor-value';
|
||||
import { getEditorViewState } from './editor-viewstate';
|
||||
|
||||
export interface EditorBackup {
|
||||
value: string;
|
||||
model: editor.ITextModel | null;
|
||||
viewState: editor.ICodeEditorViewState | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a backup for a given editor
|
||||
*
|
||||
* @param {EditorId} id
|
||||
* @returns {EditorBackup}
|
||||
*/
|
||||
export function getEditorBackup(id: EditorId): EditorBackup {
|
||||
return {
|
||||
value: getEditorValue(id),
|
||||
model: getEditorModel(id),
|
||||
viewState: getEditorViewState(id)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { editor } from 'monaco-editor';
|
||||
|
||||
import { EditorId } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Return the model for a given editor
|
||||
*
|
||||
* @param {EditorId} id
|
||||
* @returns {editor.ITextModel | null}
|
||||
*/
|
||||
export function getEditorModel(id: EditorId): editor.ITextModel | null {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
if (fiddle.editors[id] && fiddle.editors[id]!.getModel) {
|
||||
return fiddle.editors[id]!.getModel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { EditorId } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Return the value for a given editor
|
||||
*
|
||||
* @param {EditorId} id
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getEditorValue(id: EditorId): string {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
if (fiddle.editors[id] && fiddle.editors[id]!.getValue) {
|
||||
return fiddle.editors[id]!.getValue();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { editor } from 'monaco-editor';
|
||||
|
||||
import { EditorId } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Returns the view state for a given editor.
|
||||
*
|
||||
* @export
|
||||
* @param {EditorId} id
|
||||
* @returns {(editor.ICodeEditorViewState | null)}
|
||||
*/
|
||||
export function getEditorViewState(id: EditorId): editor.ICodeEditorViewState | null {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
throw new Error('Fiddle not ready');
|
||||
}
|
||||
|
||||
if (fiddle.editors[id] && fiddle.editors[id]!.saveViewState) {
|
||||
return fiddle.editors[id]!.saveViewState();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { MosaicDirection, MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import { MosaicId } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Create a mosaic arrangement given an array of editor ids.
|
||||
*
|
||||
* @export
|
||||
* @param {Array<MosaicId>} input
|
||||
* @returns {MosaicNode<MosaicId>}
|
||||
*/
|
||||
export function createMosaicArrangement(
|
||||
input: Array<MosaicId>, direction: MosaicDirection = 'row'
|
||||
): MosaicNode<MosaicId> {
|
||||
if (input.length === 1) {
|
||||
return input[0];
|
||||
}
|
||||
|
||||
// This cuts out the first half of input. Input becomes the second half.
|
||||
const secondHalf = [ ...input ];
|
||||
const firstHalf = secondHalf.splice(0, Math.floor(secondHalf.length / 2));
|
||||
|
||||
return {
|
||||
direction,
|
||||
first: createMosaicArrangement(firstHalf, 'column'),
|
||||
second: createMosaicArrangement(secondHalf, 'column')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of visible editors given a mosaic arrangement.
|
||||
*
|
||||
* @export
|
||||
* @param {MosaicNode<MosaicId> | null} input
|
||||
* @returns {Array<MosaicId>}
|
||||
*/
|
||||
export function getVisibleMosaics(input: MosaicNode<MosaicId> | null): Array<MosaicId> {
|
||||
// Handle the unlikely null case
|
||||
if (!input) return [];
|
||||
|
||||
// Handle the case where only one editor is visible
|
||||
if (typeof input === 'string') {
|
||||
return [ input ];
|
||||
}
|
||||
|
||||
// Handle the other cases (2 - 4)
|
||||
const result = [];
|
||||
for (const node of [ input.first, input.second ]) {
|
||||
if (typeof node === 'string') {
|
||||
result.push(node);
|
||||
} else {
|
||||
result.push(...getVisibleMosaics(node));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { getRandomNumber } from './random';
|
||||
|
||||
/**
|
||||
* Get a random nice greeting
|
||||
*
|
||||
* @export
|
||||
* @returns
|
||||
*/
|
||||
export function getNiceGreeting() {
|
||||
const greetings = [
|
||||
'👋 Go build a great app',
|
||||
'💖 You got this',
|
||||
`💖 We think you're great`,
|
||||
'🤘 Your app will rock, we know it',
|
||||
'🙇♀️ Thanks for trying out Electron & Fiddle',
|
||||
'🐕 Go pet a dog today',
|
||||
'🐈 Go pet a cat today',
|
||||
'💧 Stay hydrated',
|
||||
];
|
||||
|
||||
const min = 0;
|
||||
const max = greetings.length;
|
||||
|
||||
return greetings[getRandomNumber(min, max)];
|
||||
}
|
||||
+18
-3
@@ -1,15 +1,30 @@
|
||||
import * as GitHubType from '@octokit/rest';
|
||||
import { AppState } from '../renderer/state';
|
||||
|
||||
let _Octokit: typeof GitHubType;
|
||||
let _octo: GitHubType;
|
||||
|
||||
/**
|
||||
* Returns a loaded Octokit.
|
||||
* Returns a loaded Octokit. If state is passed and authentication
|
||||
* is available, we'll token-authenticate.
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<typeof GitHubType>}
|
||||
*/
|
||||
export async function getOctokit(): Promise<GitHubType> {
|
||||
export async function getOctokit(
|
||||
appState?: AppState
|
||||
): Promise<GitHubType> {
|
||||
_Octokit = _Octokit || (await import('@octokit/rest') as any).default;
|
||||
return _octo = _octo || new _Octokit();
|
||||
_octo = _octo || new _Octokit();
|
||||
|
||||
// You can load Gists without being authenticated,
|
||||
// but we get better rate limits when authenticated
|
||||
if (appState && appState.gitHubToken) {
|
||||
_octo.authenticate({
|
||||
type: 'token',
|
||||
token: appState.gitHubToken!
|
||||
});
|
||||
}
|
||||
|
||||
return _octo;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Dumb helper: Get a random number between two numbers
|
||||
*
|
||||
* @param {number} [min=0]
|
||||
* @param {number} [max=1]
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getRandomNumber(
|
||||
min: number = 0, max: number = 1
|
||||
): number {
|
||||
return Math.floor(Math.random() * max) + min;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { AboutPanelOptionsOptions, app } from 'electron';
|
||||
|
||||
/**
|
||||
* Sets Fiddle's About panel options on Linux and macOS
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function setupAboutPanel(): void {
|
||||
const options: AboutPanelOptionsOptions = {
|
||||
applicationName: 'Electron Fiddle',
|
||||
applicationVersion: app.getVersion(),
|
||||
version: process.versions.electron,
|
||||
copyright: '© Electron Authors',
|
||||
};
|
||||
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
options.website = 'https://electronjs.org/fiddle';
|
||||
case 'darwin':
|
||||
options.credits = 'https://github.com/electron/fiddle/graphs/contributors';
|
||||
default:
|
||||
// fallthrough
|
||||
}
|
||||
|
||||
app.setAboutPanelOptions(options);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ALL_EDITORS, EditorId, MosaicId, PanelId } from '../interfaces';
|
||||
import { EditorBackup } from './editor-backup';
|
||||
|
||||
/**
|
||||
* Is the given string of type `EditorId`?
|
||||
*
|
||||
* @export
|
||||
* @param {(EditorId | PanelId | MosaicId)} input
|
||||
* @returns {input is EditorId}
|
||||
*/
|
||||
export function isEditorId(input: EditorId | PanelId | MosaicId): input is EditorId {
|
||||
return (ALL_EDITORS as any).includes(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given string of type `isPanelId`?
|
||||
*
|
||||
* @export
|
||||
* @param {(EditorId | PanelId | MosaicId)} input
|
||||
* @returns {input is MosaicId}
|
||||
*/
|
||||
export function isPanelId(input: EditorId | PanelId | MosaicId): input is PanelId {
|
||||
return !(ALL_EDITORS as any).includes(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given input of type `EditorBackup`?
|
||||
*
|
||||
* @export
|
||||
* @param {(EditorBackup | true)} input
|
||||
* @returns {input is EditorBackup}
|
||||
*/
|
||||
export function isEditorBackup(input?: EditorBackup | true | null): input is EditorBackup {
|
||||
return !!(input && input !== true);
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
[{"version":"0.1.0"},{"version":"0.1.1"},{"version":"0.1.2"},{"version":"0.2.0"},{"version":"0.2.1"},{"version":"0.3.0"},{"version":"0.4.0"},{"version":"0.4.1"},{"version":"1.3.1"},{"version":"1.3.2"},{"version":"1.3.3"},{"version":"1.3.4"},{"version":"1.3.5"},{"version":"1.3.6"},{"version":"1.4.0"},{"version":"1.4.1"},{"version":"1.3.7"},{"version":"1.4.2"},{"version":"1.4.3"},{"version":"1.4.4"},{"version":"1.3.8"},{"version":"1.4.5"},{"version":"1.4.6"},{"version":"1.4.7"},{"version":"1.3.9"},{"version":"1.3.10"},{"version":"1.4.8"},{"version":"1.3.12"},{"version":"1.4.10"},{"version":"1.3.13"},{"version":"1.4.11"},{"version":"1.4.12"},{"version":"1.4.13"},{"version":"1.4.14"},{"version":"1.4.15"},{"version":"1.5.0"},{"version":"1.5.1"},{"version":"1.6.0"},{"version":"1.6.1"},{"version":"1.6.2"},{"version":"1.6.3"},{"version":"1.3.14"},{"version":"1.6.4"},{"version":"1.6.5"},{"version":"1.4.16"},{"version":"1.6.6"},{"version":"1.6.7"},{"version":"1.3.15"},{"version":"1.6.8"},{"version":"1.6.9"},{"version":"1.7.0"},{"version":"1.6.10"},{"version":"1.7.1"},{"version":"1.6.11"},{"version":"1.7.2"},{"version":"1.7.3"},{"version":"1.7.4"},{"version":"1.7.5"},{"version":"1.6.12"},{"version":"1.7.6"},{"version":"1.7.7"},{"version":"1.6.13"},{"version":"1.7.8"},{"version":"1.6.14"},{"version":"1.8.1"},{"version":"1.7.9"},{"version":"1.6.15"},{"version":"1.8.2-beta.1"},{"version":"1.8.2-beta.2"},{"version":"1.8.2-beta.3"},{"version":"1.7.10"},{"version":"1.7.11"},{"version":"1.6.16"},{"version":"1.8.2-beta.4"},{"version":"1.8.2-beta.5"},{"version":"1.7.12"},{"version":"1.6.17"},{"version":"1.8.2"},{"version":"2.0.0-beta.1"},{"version":"2.0.0-beta.2"},{"version":"1.8.3"},{"version":"2.0.0-beta.3"},{"version":"1.7.13"},{"version":"2.0.0-beta.4"},{"version":"1.8.4"},{"version":"2.0.0-beta.5"},{"version":"2.0.0-beta.6"},{"version":"2.0.0-beta.7"},{"version":"2.0.0-beta.8"},{"version":"1.8.5"},{"version":"1.7.14"},{"version":"1.8.6"},{"version":"2.0.0"},{"version":"1.6.18"},{"version":"1.7.15"},{"version":"1.8.7"},{"version":"2.0.1"},{"version":"2.0.2"},{"version":"3.0.0-beta.1"},{"version":"2.0.3"},{"version":"2.0.4"},{"version":"3.0.0-beta.2"},{"version":"2.0.5"},{"version":"3.0.0-beta.3"},{"version":"2.0.6"},{"version":"3.0.0-beta.4"},{"version":"2.0.7"},{"version":"2.1.0-unsupported.20180809"},{"version":"3.0.0-beta.5"},{"version":"4.0.0-nightly.20180816"},{"version":"4.0.0-nightly.20180817"},{"version":"3.0.0-nightly.20180818"},{"version":"4.0.0-nightly.20180819"},{"version":"2.0.8-nightly.20180819"},{"version":"3.0.0-beta.6"},{"version":"2.0.8-nightly.20180820"},{"version":"3.0.0-nightly.20180821"},{"version":"4.0.0-nightly.20180821"},{"version":"3.0.0-beta.7"},{"version":"2.0.8"},{"version":"1.8.8"},{"version":"1.7.16"},{"version":"3.0.0-beta.8"},{"version":"3.0.0-nightly.20180904"},{"version":"3.0.0-beta.9"},{"version":"3.0.0-beta.10"},{"version":"2.0.9"},{"version":"3.0.0-beta.11"},{"version":"3.0.0-beta.12"},{"version":"3.0.0-beta.13"},{"version":"3.0.0"},{"version":"2.0.10"},{"version":"3.0.1"},{"version":"3.0.2"},{"version":"4.0.0-nightly.20180929"},{"version":"2.0.11"},{"version":"4.0.0-nightly.20181006"},{"version":"3.0.3"},{"version":"4.0.0-nightly.20181010"},{"version":"3.0.4"},{"version":"4.0.0-beta.1"},{"version":"4.0.0-beta.2"},{"version":"4.0.0-beta.3"},{"version":"3.0.5"},{"version":"2.0.12"},{"version":"4.0.0-beta.4"},{"version":"4.0.0-beta.5"},{"version":"3.0.6"},{"version":"3.0.7"},{"version":"2.0.13"},{"version":"4.0.0-beta.6"},{"version":"3.0.8"},{"version":"4.0.0-beta.7"},{"version":"3.0.9"},{"version":"2.0.14"},{"version":"3.0.10"},{"version":"4.0.0-beta.8"},{"version":"3.1.0-beta.1"},{"version":"3.1.0-beta.2"},{"version":"3.0.11"},{"version":"4.0.0-beta.9"},{"version":"2.0.15"},{"version":"3.0.12"},{"version":"3.1.0-beta.3"},{"version":"3.0.13"},{"version":"3.1.0-beta.4"},{"version":"4.0.0-beta.10"}]
|
||||
[{"version":"0.1.0"},{"version":"0.1.1"},{"version":"0.1.2"},{"version":"0.2.0"},{"version":"0.2.1"},{"version":"0.3.0"},{"version":"0.4.0"},{"version":"0.4.1"},{"version":"1.3.1"},{"version":"1.3.2"},{"version":"1.3.3"},{"version":"1.3.4"},{"version":"1.3.5"},{"version":"1.3.6"},{"version":"1.4.0"},{"version":"1.4.1"},{"version":"1.3.7"},{"version":"1.4.2"},{"version":"1.4.3"},{"version":"1.4.4"},{"version":"1.3.8"},{"version":"1.4.5"},{"version":"1.4.6"},{"version":"1.4.7"},{"version":"1.3.9"},{"version":"1.3.10"},{"version":"1.4.8"},{"version":"1.3.12"},{"version":"1.4.10"},{"version":"1.3.13"},{"version":"1.4.11"},{"version":"1.4.12"},{"version":"1.4.13"},{"version":"1.4.14"},{"version":"1.4.15"},{"version":"1.5.0"},{"version":"1.5.1"},{"version":"1.6.0"},{"version":"1.6.1"},{"version":"1.6.2"},{"version":"1.6.3"},{"version":"1.3.14"},{"version":"1.6.4"},{"version":"1.6.5"},{"version":"1.4.16"},{"version":"1.6.6"},{"version":"1.6.7"},{"version":"1.3.15"},{"version":"1.6.8"},{"version":"1.6.9"},{"version":"1.7.0"},{"version":"1.6.10"},{"version":"1.7.1"},{"version":"1.6.11"},{"version":"1.7.2"},{"version":"1.7.3"},{"version":"1.7.4"},{"version":"1.7.5"},{"version":"1.6.12"},{"version":"1.7.6"},{"version":"1.7.7"},{"version":"1.6.13"},{"version":"1.7.8"},{"version":"1.6.14"},{"version":"1.8.1"},{"version":"1.7.9"},{"version":"1.6.15"},{"version":"1.8.2-beta.1"},{"version":"1.8.2-beta.2"},{"version":"1.8.2-beta.3"},{"version":"1.7.10"},{"version":"1.7.11"},{"version":"1.6.16"},{"version":"1.8.2-beta.4"},{"version":"1.8.2-beta.5"},{"version":"1.7.12"},{"version":"1.6.17"},{"version":"1.8.2"},{"version":"2.0.0-beta.1"},{"version":"2.0.0-beta.2"},{"version":"1.8.3"},{"version":"2.0.0-beta.3"},{"version":"1.7.13"},{"version":"2.0.0-beta.4"},{"version":"1.8.4"},{"version":"2.0.0-beta.5"},{"version":"2.0.0-beta.6"},{"version":"2.0.0-beta.7"},{"version":"2.0.0-beta.8"},{"version":"1.8.5"},{"version":"1.7.14"},{"version":"1.8.6"},{"version":"2.0.0"},{"version":"1.6.18"},{"version":"1.7.15"},{"version":"1.8.7"},{"version":"2.0.1"},{"version":"2.0.2"},{"version":"3.0.0-beta.1"},{"version":"2.0.3"},{"version":"2.0.4"},{"version":"3.0.0-beta.2"},{"version":"2.0.5"},{"version":"3.0.0-beta.3"},{"version":"2.0.6"},{"version":"3.0.0-beta.4"},{"version":"2.0.7"},{"version":"2.1.0-unsupported.20180809"},{"version":"3.0.0-beta.5"},{"version":"4.0.0-nightly.20180816"},{"version":"4.0.0-nightly.20180817"},{"version":"3.0.0-nightly.20180818"},{"version":"4.0.0-nightly.20180819"},{"version":"2.0.8-nightly.20180819"},{"version":"3.0.0-beta.6"},{"version":"2.0.8-nightly.20180820"},{"version":"3.0.0-nightly.20180821"},{"version":"4.0.0-nightly.20180821"},{"version":"3.0.0-beta.7"},{"version":"2.0.8"},{"version":"1.8.8"},{"version":"1.7.16"},{"version":"3.0.0-beta.8"},{"version":"3.0.0-nightly.20180904"},{"version":"3.0.0-beta.9"},{"version":"3.0.0-beta.10"},{"version":"2.0.9"},{"version":"3.0.0-beta.11"},{"version":"3.0.0-beta.12"},{"version":"3.0.0-beta.13"},{"version":"3.0.0"},{"version":"2.0.10"},{"version":"3.0.1"},{"version":"3.0.2"},{"version":"4.0.0-nightly.20180929"},{"version":"2.0.11"},{"version":"4.0.0-nightly.20181006"},{"version":"3.0.3"},{"version":"4.0.0-nightly.20181010"},{"version":"3.0.4"},{"version":"4.0.0-beta.1"},{"version":"4.0.0-beta.2"},{"version":"4.0.0-beta.3"},{"version":"3.0.5"},{"version":"2.0.12"},{"version":"4.0.0-beta.4"},{"version":"4.0.0-beta.5"},{"version":"3.0.6"},{"version":"3.0.7"},{"version":"2.0.13"},{"version":"4.0.0-beta.6"},{"version":"3.0.8"},{"version":"4.0.0-beta.7"},{"version":"3.0.9"},{"version":"2.0.14"},{"version":"3.0.10"},{"version":"4.0.0-beta.8"},{"version":"3.1.0-beta.1"},{"version":"3.1.0-beta.2"},{"version":"3.0.11"},{"version":"4.0.0-beta.9"},{"version":"2.0.15"},{"version":"3.0.12"},{"version":"3.1.0-beta.3"},{"version":"3.0.13"},{"version":"3.1.0-beta.4"},{"version":"4.0.0-beta.10"},{"version":"2.0.16"},{"version":"4.0.0-beta.11"},{"version":"4.0.0"},{"version":"3.1.0-beta.5"},{"version":"4.0.1"},{"version":"3.0.14"},{"version":"3.1.0"},{"version":"3.1.1"},{"version":"4.0.2"},{"version":"5.0.0-beta.1"},{"version":"3.1.2"},{"version":"4.0.3"},{"version":"2.0.17"},{"version":"3.0.15"},{"version":"3.1.3"},{"version":"4.0.4"},{"version":"5.0.0-beta.2"},{"version":"4.0.5"},{"version":"5.0.0-beta.3"},{"version":"3.1.4"},{"version":"4.0.6"},{"version":"5.0.0-beta.4"},{"version":"3.1.5"},{"version":"4.0.7"},{"version":"5.0.0-beta.5"},{"version":"2.0.18"},{"version":"3.1.6"},{"version":"3.0.16"},{"version":"4.0.8"},{"version":"4.1.0"},{"version":"5.0.0-beta.6"},{"version":"4.1.1"},{"version":"3.1.7"},{"version":"5.0.0-beta.7"}]
|
||||
@@ -8,7 +8,13 @@ const { app, BrowserWindow } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
||||
@@ -8,7 +8,13 @@ const { app, BrowserWindow } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
mainWindow.webContents.on('crashed', () => {
|
||||
@@ -9,7 +9,13 @@ const { app, BrowserWindow } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
||||
@@ -10,7 +10,13 @@ const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('asynchronous-message', (event, arg) => {
|
||||
console.log(arg) // prints "ping"
|
||||
@@ -9,7 +9,13 @@ const { app, BrowserWindow, Notification } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
if (Notification.isSupported()) {
|
||||
const notification = new Notification({
|
||||
@@ -8,6 +8,12 @@ const { app, BrowserWindow } = require('electron')
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
||||
@@ -17,7 +17,13 @@ app.on('ready', () => {
|
||||
const primaryDisplay = screen.getPrimaryDisplay()
|
||||
const { width, height } = primaryDisplay.workAreaSize
|
||||
|
||||
mainWindow = new BrowserWindow({ width, height })
|
||||
mainWindow = new BrowserWindow({
|
||||
width,
|
||||
height,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user