Compare commits

...
Author SHA1 Message Date
Erick Zhao 3153cfc4b7 refactor: move about-panel function 2020-07-03 11:44:23 -07:00
Shelley Vohr 9065a8678a fix: audio/video entitlements crash (#426)
* fix: add entitlements for audio and video capture

* chore: add usageDescription

* empty commit
2020-07-01 12:49:39 -07:00
github-actions[bot]andmalept 016d3ee78a build: update Electron releases JSON (#419)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-06-29 20:02:40 +03:00
Shelley Vohr 4b40420bba feat: log notification info in adv logging (#421) 2020-06-25 11:30:28 -07:00
Vlad Hashimoto 0e872b0dd0 feat: allow to publish if user would to do it (#413) 2020-06-18 15:11:06 +03:00
github-actions[bot]andmalept 7638111598 build: update Electron releases JSON (#414)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-06-16 08:14:39 -07:00
Benjamin Sova a0e59ad288 docs: fix explanation of darwin auto-quit prevention (#410) 2020-06-11 09:48:38 -07:00
Erick Zhao 3c6103dc50 chore: 0.15.1 (#409) 2020-06-09 10:24:32 -07:00
Erick Zhao b98472f973 fix: make hidden editors interact properly with remote content (#404) 2020-06-08 16:37:20 -07:00
Jeremy Rose 3602eacc88 fix: correct sense of bisect buttons (#408) 2020-06-08 16:19:54 -07:00
github-actions[bot] 8b84c040c7 build: update Electron releases JSON (#399) 2020-06-07 20:36:37 -07:00
Erick Zhao 6aa19eada6 fix: proper async/await usage in editor.tsx (#403) 2020-06-01 14:39:41 -07:00
Erick Zhao 8e92183de2 refactor: replace app.getName() with app.name (#401) 2020-05-27 16:03:17 -07:00
Vlad Hashimoto 1a951f3f44 feat: v0.15.0 (#398) 2020-05-23 02:46:28 +03:00
Will Anderson 66ead93f05 fix: Update documentation link for user-provided flags (#397) 2020-05-23 00:26:09 +03:00
Vlad Hashimoto d22899b4a5 feat: add ability to update gist instead of publishing (#386) 2020-05-22 23:59:27 +03:00
Vlad Hashimoto 77d4bc6fc5 fix: use role: quit for render exit button in menu (#395) 2020-05-21 00:45:41 +03:00
github-actions[bot] 961d42d74f build: update Electron releases JSON (#393) 2020-05-20 21:33:13 +03:00
Vlad Hashimoto c58ade2132 fix: reset github token when authentication failed (#388) 2020-05-20 21:31:00 +03:00
github-actions[bot] 592c220f18 build: update Electron releases JSON (#391) 2020-05-13 21:13:51 +03:00
Vlad Hashimoto 4da0e82e9b fix: set the window to null after destroying (#389) 2020-05-13 20:54:18 +03:00
Vlad Hashimoto f766bef10c fix: doesn't error when unable to watch file (#390) 2020-05-12 19:36:28 -07:00
Natalie 30ba9fb023 docs: improve CONTRIBUTING.md (#380)
This PR adds a Contributing section to the README and descriptive text for running Fiddle from source in CONTRIBUTING.md.
2020-05-12 08:53:17 -07:00
github-actions[bot] f3e847da85 build: update Electron releases JSON (#381) 2020-05-04 23:42:14 -07:00
Kimberlee Johnson 0f854441e4 feat: reset layout button (#374) 2020-04-29 17:35:18 -07:00
Vlad Hashimoto f31a4e38bb fix: don't modify state directly (#385) 2020-04-28 14:18:18 -07:00
Felix Rieseberg 26bb3e4944 Merge pull request #373 from codebytere/update-electron-get
chore: update @electron/get version
2020-04-21 11:03:20 -07:00
Vlad Hashimoto fb1fb7869c Merge pull request #376 from electron/update-releases
build: update Electron releases JSON
2020-04-20 12:21:15 +03:00
malept a1e67400bf build: update Electron releases JSON 2020-04-20 00:04:55 +00:00
Shelley Vohr a26aa1e70c chore: update @electron/get version 2020-04-14 14:22:25 -07:00
github-actions[bot]andElectron Bot 78395412f4 build: update Electron releases JSON (#368)
Co-authored-by: Electron Bot <electron-bot@users.noreply.github.com>
2020-04-13 07:51:52 -07:00
Felix Rieseberg 1f72db3cfc 0.14.0 2020-04-06 10:22:19 -07:00
Felix Rieseberg c333a1e132 Merge pull request #366 from electron/update-releases
build: update Electron releases JSON
2020-04-06 10:19:49 -07:00
malept a0a989d7a7 build: update Electron releases JSON 2020-04-06 00:13:17 +00:00
github-actions[bot]andmalept 5b07b8a706 build: update Electron releases JSON (#364)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-03-30 03:20:17 +03:00
43 changed files with 5190 additions and 8534 deletions
+18 -1
View File
@@ -39,10 +39,27 @@ Here are some things to keep in mind as you file pull requests to fix bugs, add
### Running Fiddle From Source
1. Clone the Electron Fiddle repository locally.
```sh
git clone https://github.com/electron/fiddle.git
```
2. Change directory to where Fiddle has been cloned.
```sh
git clone https://github.com/electron/fiddle
cd fiddle
```
3. Install dependencies.
```sh
npm install
```
4. Start Fiddle and Explore!
```sh
npm start
```
+4
View File
@@ -56,6 +56,10 @@ fiddle has grown up, export it as a project with or without
[electron-forge][electron-forge]. Then, use your favorite editor and take on
the world!
## Contributing
Electron Fiddle is a community-driven project that welcomes all sorts of contributions. Please check out our [Contributing Guide](https://github.com/electron/fiddle/blob/master/CONTRIBUTING.md) for more details.
## License
[MIT, please see the LICENSE file for full details](https://github.com/electron/fiddle/blob/master/LICENSE.md).
+4 -5
View File
@@ -16,12 +16,11 @@ const config = {
executableName: 'electron-fiddle',
asar: true,
icon: path.resolve(__dirname, 'assets', 'icons', 'fiddle'),
// TODO: FIXME?
// ignore: [
// /^\/\.vscode\//,
// /^\/tools\//
// ],
appBundleId: 'com.electron.fiddle',
usageDescription: {
Camera: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Camera',
Microphone: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Microphone'
},
appCategoryType: 'public.app-category.developer-tools',
protocols: [{
name: 'Electron Fiddle Launch Protocol',
+4658 -8350
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.13.1",
"version": "0.15.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
+3 -3
View File
@@ -23,10 +23,10 @@ function createWindow () {
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
+1 -1
View File
@@ -4,7 +4,7 @@ initSentry();
import { app } from 'electron';
import { isDevMode } from '../utils/devmode';
import { setupAboutPanel } from '../utils/set-about-panel';
import { setupAboutPanel } from './about-panel';
import { setupDevTools } from './devtools';
import { setupDialogs } from './dialogs';
import { onFirstRunMaybe } from './first-run';
+2 -4
View File
@@ -105,9 +105,7 @@ function getQuitItems(): Array<MenuItemConstructorOptions> {
{
type: 'separator'
}, {
label: 'Exit',
accelerator: 'Ctrl+Q,',
click: app.quit
role: 'quit'
}
];
}
@@ -239,7 +237,7 @@ export function setupMenu() {
// Append the "Settings" item
if (
process.platform === 'darwin'
&& label === app.getName()
&& label === app.name
&& isSubmenu(item.submenu)
) {
item.submenu.splice(2, 0, ...getPreferencesItems());
+10 -5
View File
@@ -5,7 +5,7 @@ import { ipcMainManager } from './ipc';
// Keep a global reference of the window objects, if we don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
export let browserWindows: Array<BrowserWindow> = [];
export let browserWindows: Array<BrowserWindow | null> = [];
/**
* Gets default options for the main window
@@ -37,13 +37,14 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
*/
export function createMainWindow(): Electron.BrowserWindow {
console.log(`Creating main window`);
const browserWindow = new BrowserWindow(getMainWindowOptions());
let browserWindow: BrowserWindow | null;
browserWindow = new BrowserWindow(getMainWindowOptions());
browserWindow.loadFile('./dist/static/index.html');
browserWindow.webContents.once('dom-ready', () => {
browserWindow.show();
if (browserWindow) {
browserWindow.show();
createContextMenu(browserWindow);
}
});
@@ -51,6 +52,8 @@ export function createMainWindow(): Electron.BrowserWindow {
browserWindow.on('closed', () => {
browserWindows = browserWindows
.filter((bw) => browserWindow !== bw);
browserWindow = null;
});
browserWindow.webContents.on('new-window', (event, url) => {
@@ -64,7 +67,9 @@ export function createMainWindow(): Electron.BrowserWindow {
});
ipcMainManager.on(IpcEvents.SHOW_INACTIVE, () => {
browserWindow.showInactive();
if (browserWindow) {
browserWindow.showInactive();
}
});
browserWindows.push(browserWindow);
+14 -2
View File
@@ -18,6 +18,7 @@ import { getEditorValue } from '../utils/editor-value';
import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
import { getTitle } from '../utils/get-title';
import { isEditorBackup } from '../utils/type-checks';
import { EMPTY_EDITOR_CONTENT } from './constants';
import { FileManager } from './file-manager';
import { RemoteLoader } from './remote-loader';
import { Runner } from './runner';
@@ -68,8 +69,17 @@ export class App {
}
}
// set values once prompt approves
await this.setEditorValues(editorValues);
// display all editors that have content
const visibleEditors = [];
for (const id of Object.keys(editorValues)) {
const content = editorValues[id];
// if the gist content matches the empty file output, don't show it
if (!Object.values(EMPTY_EDITOR_CONTENT).includes(content)) {
visibleEditors.push(id as EditorId);
}
}
document.title = getTitle(this.state);
this.state.gistId = gistId || '';
@@ -77,6 +87,8 @@ export class App {
this.state.templateName = templateName;
// once loaded, we have a "saved" state
await this.state.setVisibleMosaics(visibleEditors);
await this.setEditorValues(editorValues);
this.state.isUnsaved = false;
return true;
+13 -1
View File
@@ -1,4 +1,4 @@
import { Button, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -82,6 +82,18 @@ export class EditorDropdown extends React.Component<EditorDropdownProps, EditorD
);
}
result.push(
<React.Fragment key={'fragment-reset-layout'}>
<MenuDivider />
<MenuItem
icon='grid-view'
key='reset-layout'
text='Reset Layout'
onClick={appState.resetEditorLayout}
/>
</React.Fragment>
);
return result;
}
@@ -1,11 +1,13 @@
import { Button, ButtonGroup, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { Button, ButtonGroup, IToastProps, Menu, MenuItem, Popover, Position, Toaster } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { when } from 'mobx';
import { EditorValues } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../../shared-constants';
import { getOctokit } from '../../utils/octokit';
import { EMPTY_EDITOR_CONTENT } from '../constants';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -13,6 +15,11 @@ export interface PublishButtonProps {
appState: AppState;
}
interface IPublishButtonState {
readonly isUpdating: boolean;
readonly wouldPublish: boolean;
}
/**
* The "publish" button takes care of logging you in.
*
@@ -21,15 +28,25 @@ export interface PublishButtonProps {
* @extends {React.Component<PublishButtonProps, PublishButtonState>}
*/
@observer
export class PublishButton extends React.Component<PublishButtonProps> {
constructor(props: PublishButtonProps) {
export class PublishButton extends React.Component<PublishButtonProps, IPublishButtonState> {
public constructor(props: PublishButtonProps) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.publishFiddle = this.publishFiddle.bind(this);
this.publishOrUpdateFiddle = this.publishOrUpdateFiddle.bind(this);
this.setPrivate = this.setPrivate.bind(this);
this.setPublic = this.setPublic.bind(this);
this.state = {
isUpdating: false,
wouldPublish: false,
};
}
private toaster: Toaster;
private refHandlers = {
toaster: (ref: Toaster) => this.toaster = ref,
};
public componentDidMount() {
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
}
@@ -60,7 +77,7 @@ export class PublishButton extends React.Component<PublishButtonProps> {
await when(() => !!appState.gitHubToken || !appState.isTokenDialogShowing);
if (appState.gitHubToken) {
return this.publishFiddle();
return this.publishOrUpdateFiddle();
}
}
@@ -68,50 +85,52 @@ export class PublishButton extends React.Component<PublishButtonProps> {
* Connect with GitHub, publish the current Fiddle as a gist,
* and update all related properties in the app state.
*/
public async publishFiddle(): Promise<void> {
public async publishOrUpdateFiddle(): Promise<void> {
const { appState } = this.props;
const { wouldPublish } = this.state;
appState.isPublishing = true;
const octo = await getOctokit(this.props.appState);
const { gitHubPublishAsPublic } = this.props.appState;
const { gitHubPublishAsPublic, gistId } = this.props.appState;
const options = { includeDependencies: true, includeElectron: true };
const values = await window.ElectronFiddle.app.getEditorValues(options);
try {
const gist = await octo.gists.create({
public: !!gitHubPublishAsPublic,
description: 'Electron Fiddle Gist',
files: {
[INDEX_HTML_NAME]: {
content: values.html || '<!-- Empty -->',
},
[MAIN_JS_NAME]: {
content: values.main || '// Empty',
},
[RENDERER_JS_NAME]: {
content: values.renderer || '// Empty',
},
[PRELOAD_JS_NAME]: {
content: values.preload || '// Empty',
},
[STYLES_CSS_NAME]: {
content: values.css || '/* Empty */',
},
},
} as any); // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
if (gistId && !wouldPublish) {
this.setState({
isUpdating: true
});
const gist = await octo.gists.update({
gist_id: appState.gistId,
files: this.gistFilesList(values) as any,
});
appState.gistId = gist.data.id;
console.log('Updating: Updating done', { gist });
this.renderToast({ message: 'Successfully updated gist!' });
this.setState({
isUpdating: false
});
} else {
try {
const gist = await octo.gists.create({
public: !!gitHubPublishAsPublic,
description: 'Electron Fiddle Gist',
files: this.gistFilesList(values) as any, // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
});
console.log(`Publish Button: Publishing done`, { gist });
} catch (error) {
console.warn(`Could not publish gist`, { error });
appState.gistId = gist.data.id;
const messageBoxOptions: Electron.MessageBoxOptions = {
message: 'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
detail: `GitHub encountered the following error: ${error.message}`
};
console.log(`Publish Button: Publishing done`, { gist });
this.renderToast({ message: 'Publishing done successfully!' });
} catch (error) {
console.warn(`Could not publish gist`, { error });
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
const messageBoxOptions: Electron.MessageBoxOptions = {
message: 'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
detail: `GitHub encountered the following error: ${error.message}`
};
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
}
}
appState.isPublishing = false;
@@ -136,8 +155,77 @@ export class PublishButton extends React.Component<PublishButtonProps> {
}
public render() {
const { gitHubPublishAsPublic } = this.props.appState;
const { isPublishing } = this.props.appState;
const { isPublishing, gistId } = this.props.appState;
const { isUpdating, wouldPublish } = this.state;
const getTextForButton = gistId && !wouldPublish
? 'Update'
: isUpdating
? 'Updating...'
: isPublishing
? 'Publishing...'
: 'Publish';
return (
<>
<fieldset disabled={isPublishing}>
<ButtonGroup className='button-publish'>
{this.renderPrivaryMenu()}
<Button
onClick={this.handleClick}
loading={isPublishing}
icon='upload'
text={getTextForButton}
/>
{this.renderMaybePublishMenu()}
</ButtonGroup>
</fieldset>
<Toaster position={Position.BOTTOM_RIGHT} ref={this.refHandlers.toaster} />
</>
);
}
private renderMaybePublishMenu = () => {
const { gistId } = this.props.appState;
const { wouldPublish } = this.state;
if (!gistId) {
return null;
}
const menu = (
<Menu>
<MenuItem
text='Publish'
active={wouldPublish}
onClick={() => this.setWouldPublish(true)}
/>
<MenuItem
text='Update'
active={!wouldPublish}
onClick={() => this.setWouldPublish(false)}
/>
</Menu>
);
return (
<Popover
content={menu}
position={Position.BOTTOM}
>
<Button
icon='wrench'
/>
</Popover>
);
}
private renderPrivaryMenu = () => {
const { gitHubPublishAsPublic, gistId } = this.props.appState;
if (gistId) {
return null;
}
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
const privacyMenu = (
@@ -158,28 +246,48 @@ export class PublishButton extends React.Component<PublishButtonProps> {
);
return (
<fieldset disabled={isPublishing}>
<ButtonGroup className='button-publish'>
<Popover
content={privacyMenu}
position={Position.BOTTOM}
>
<Button
icon={privacyIcon}
/>
</Popover>
<Button
onClick={this.handleClick}
loading={isPublishing}
icon='upload'
text={isPublishing ? 'Publishing...' : 'Publish'}
/>
</ButtonGroup>
</fieldset>
<Popover
content={privacyMenu}
position={Position.BOTTOM}
>
<Button
icon={privacyIcon}
/>
</Popover>
);
}
private setWouldPublish = (wouldPublish: boolean) => {
this.setState({
wouldPublish,
});
}
private setPrivacy(publishAsPublic: boolean) {
this.props.appState.gitHubPublishAsPublic = publishAsPublic;
}
private renderToast = (toast: IToastProps) => {
this.toaster.show(toast);
}
private gistFilesList = (values: EditorValues) => {
return {
[INDEX_HTML_NAME]: {
content: values.html || EMPTY_EDITOR_CONTENT.html,
},
[MAIN_JS_NAME]: {
content: values.main || EMPTY_EDITOR_CONTENT.js,
},
[RENDERER_JS_NAME]: {
content: values.renderer || EMPTY_EDITOR_CONTENT.js,
},
[PRELOAD_JS_NAME]: {
content: values.preload || EMPTY_EDITOR_CONTENT.js,
},
[STYLES_CSS_NAME]: {
content: values.css || EMPTY_EDITOR_CONTENT.css,
},
};
}
}
+2 -1
View File
@@ -66,7 +66,8 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
}
const bisectRange = allVersions
.slice(endIndex, startIndex + 1);
.slice(endIndex, startIndex + 1)
.reverse();
appState.Bisector = new Bisector(bisectRange);
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
+1
View File
@@ -69,6 +69,7 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
} catch (error) {
console.warn(`Authenticating against GitHub failed`, error);
this.setState({ verifying: false, error: true });
this.props.appState.gitHubToken = null;
return;
}
+4 -4
View File
@@ -45,8 +45,8 @@ export class Editor extends React.Component<EditorProps> {
return false;
}
public componentDidMount() {
this.initMonaco();
public async componentDidMount() {
await this.initMonaco();
}
public componentWillUnmount() {
@@ -120,7 +120,7 @@ export class Editor extends React.Component<EditorProps> {
* @private
* @param {string} value
*/
private async createModel(value: string) {
private createModel(value: string) {
const { monaco } = this.props;
const model = monaco.editor.createModel(value, this.language);
@@ -158,7 +158,7 @@ export class Editor extends React.Component<EditorProps> {
}
} else {
const value = await getContent(id, version);
await this.createModel(value);
this.createModel(value);
}
}
}
+2 -2
View File
@@ -301,10 +301,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
if (!this.state || !this.state.isMounted) {
this.state = {
this.setState({
monaco,
monacoOptions: defaultMonacoOptions
};
});
} else {
this.setState({ monaco });
}
@@ -110,7 +110,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
<FormGroup>
<p>
Electron allows starting the executable with <a
href='https://electronjs.org/docs/api/chrome-command-line-switches'
href='https://www.electronjs.org/docs/api/command-line-switches'
>
user-provided flags
</a>
+10 -2
View File
@@ -3,7 +3,7 @@ import * as path from 'path';
import { MosaicNode } from 'react-mosaic-component';
import { EditorId, MosaicId } from '../interfaces';
import { EditorBackup } from '../utils/editor-backup';
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
// Reminder: When testing, this file is mocked in tests/setup.js
@@ -21,7 +21,15 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
};
export const DEFAULT_CLOSED_PANELS: Partial<Record<MosaicId, EditorBackup | true>> = {
docsDemo: true
docsDemo: true,
preload: getEditorBackup(EditorId.preload),
css: getEditorBackup(EditorId.css),
};
export const EMPTY_EDITOR_CONTENT = {
html: '<!-- Empty -->',
js: '// Empty',
css: '/* Empty */'
};
export const ELECTRON_ORG = 'electron';
+2
View File
@@ -193,9 +193,11 @@ export class Runner {
const env = { ...process.env };
if (this.appState.isEnablingElectronLogging) {
env.ELECTRON_ENABLE_LOGGING = 'true';
env.ELECTRON_DEBUG_NOTIFICATIONS = 'true';
env.ELECTRON_ENABLE_STACK_DUMPING = 'true';
} else {
delete env.ELECTRON_ENABLE_LOGGING;
delete env.ELECTRON_DEBUG_NOTIFICATIONS;
delete env.ELECTRON_ENABLE_STACK_DUMPING;
}
+29 -6
View File
@@ -32,6 +32,7 @@ import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch
import { ipcRendererManager } from './ipc';
import { activateTheme } from './themes';
import { waitForEditorsToMount } from '../utils/editor-mounted';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import {
addLocalVersion,
@@ -139,6 +140,7 @@ export class AppState {
this.toggleSettings = this.toggleSettings.bind(this);
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
this.updateElectronVersions = this.updateElectronVersions.bind(this);
this.resetEditorLayout = this.resetEditorLayout.bind(this);
ipcRendererManager.removeAllListeners(IpcEvents.OPEN_SETTINGS);
ipcRendererManager.removeAllListeners(IpcEvents.SHOW_WELCOME_TOUR);
@@ -465,10 +467,14 @@ export class AppState {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typePath = getLocalTypePathForVersion(versionObject);
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
this.localTypeWatcher = fs.watch(typePath!, async () => {
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
await updateEditorTypeDefinitions(versionObject);
});
try {
this.localTypeWatcher = fs.watch(typePath!, async () => {
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
await updateEditorTypeDefinitions(versionObject);
});
} catch (err) {
console.info('TypeDefs: Unable to start watching.');
}
} else {
if (!!this.localTypeWatcher) {
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
@@ -539,7 +545,6 @@ export class AppState {
this.outputBuffer += strData;
strData = this.outputBuffer;
const parts = strData.split('\r\n');
for (let partIndex = 0; partIndex < parts.length; partIndex += 1) {
const part = parts[partIndex];
if (partIndex === parts.length - 1) {
@@ -592,7 +597,7 @@ export class AppState {
return null;
}
@action public setVisibleMosaics(visible: Array<MosaicId>) {
@action public async setVisibleMosaics(visible: Array<MosaicId>) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
for (const id of ALL_MOSAICS) {
@@ -600,6 +605,9 @@ export class AppState {
this.closedPanels[id] = isEditorId(id)
? getEditorBackup(id)
: true;
// if we have backup, remove active editor
delete window.ElectronFiddle.editors[id];
}
// Remove the backup for panels now. Editors will remove their
@@ -613,6 +621,12 @@ export class AppState {
console.log(`State: Setting visible mosaic panels`, visible, updatedArrangement);
this.mosaicArrangement = updatedArrangement;
// after the mosaicArrangement loads, we want to wait for the Mosaic editors to
// mount to ensure that we can load content into the editors as soon as they're
// declared visible.
await waitForEditorsToMount(visible);
}
/**
@@ -635,6 +649,15 @@ export class AppState {
this.setVisibleMosaics([...currentlyVisible, id]);
}
/**
* Resets editor view to default layout
*
*
*/
@action public resetEditorLayout() {
this.mosaicArrangement = DEFAULT_MOSAIC_ARRANGEMENT;
}
/**
* Resets the view, optionally with certain view flags enabled.
*
+1 -1
View File
@@ -12,7 +12,7 @@ export function getEditorModel(id: EditorId): editor.ITextModel | null {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return null;
}
if (fiddle.editors[id] && fiddle.editors[id]!.getModel) {
+24
View File
@@ -0,0 +1,24 @@
import { MosaicId } from '../interfaces';
/**
* Waits for editors to mount on a list of Mosaic IDs
* @param editors
*/
export function waitForEditorsToMount(editors: Array<MosaicId>) {
let time = 0;
const maxTime = 3000;
const interval = 100;
return new Promise((resolve, reject) => {
(function checkMountedEditors() {
const allMounted = editors.every((id) => !!window.ElectronFiddle.editors[id]);
if (allMounted) {
return resolve();
}
time += interval;
if (time > maxTime) {
return reject(`Timed out after ${maxTime}ms: can't mount editors onto mosaics.`);
}
setTimeout(checkMountedEditors, 100);
})();
});
}
+8 -2
View File
@@ -1,4 +1,5 @@
import { EditorId } from '../interfaces';
import { EditorBackup } from './editor-backup';
/**
* Return the value for a given editor
@@ -10,11 +11,16 @@ export function getEditorValue(id: EditorId): string {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return '';
}
if (fiddle.editors[id] && fiddle.editors[id]!.getValue) {
const editor = fiddle.editors[id];
const backup = fiddle.app?.state?.closedPanels[id] as EditorBackup;
if (editor?.getValue) {
return fiddle.editors[id]!.getValue();
} else if (backup?.value) {
return backup.value;
}
return '';
+1 -1
View File
@@ -13,7 +13,7 @@ export function getEditorViewState(id: EditorId): editor.ICodeEditorViewState |
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return null;
}
if (fiddle.editors[id] && fiddle.editors[id]!.saveViewState) {
+4
View File
@@ -8,5 +8,9 @@
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -13,5 +13,5 @@ app.on('browser-window-created', () => console.log('A window was created!'))
app.on('browser-window-focus', () => console.log('...and focused!'))
console.log(`The app lives at: ${app.getAppPath()}`)
console.log(`It's named: ${app.getName()}`)
console.log(`It's named: ${app.name}`)
console.log(`It has the version: ${app.getVersion()}`)
+2 -2
View File
@@ -1,4 +1,4 @@
{
"expectedVersionCount": 325,
"lastElectronVersion": "9.0.0-beta.9"
"expectedVersionCount": 370,
"lastElectronVersion": "8.3.4"
}
+2 -2
View File
@@ -8,13 +8,13 @@ import { main, onBeforeQuit, onReady, onWindowsAllClosed } from '../../src/main/
import { shouldQuit } from '../../src/main/squirrel';
import { setupUpdates } from '../../src/main/update';
import { getOrCreateMainWindow } from '../../src/main/windows';
import { setupAboutPanel } from '../../src/utils/set-about-panel';
import { setupAboutPanel } from '../../src/main/about-panel';
jest.mock('../../src/main/windows', () => ({
getOrCreateMainWindow: jest.fn()
}));
jest.mock('../../src/utils/set-about-panel', () => ({
jest.mock('../../src/main/about-panel', () => ({
setupAboutPanel: jest.fn()
}));
+2
View File
@@ -15,6 +15,8 @@ jest.mock('../../src/main/ipc');
describe('menu', () => {
beforeEach(() => {
electron.app.name = 'Electron Fiddle';
// need to keep this deprecated API for electron-default-menu mock as well
(electron.app.getName as any).mockReturnValue('Electron Fiddle');
(electron.dialog.showOpenDialog as any).mockReturnValue(Promise.resolve({}));
});
+5
View File
@@ -114,6 +114,7 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.state.setVisibleMosaics = jest.fn();
app.setEditorValues = jest.fn();
const editorValues = {
@@ -143,6 +144,7 @@ describe('Editors component', () => {
app.state.isUnsaved = true;
app.state.localPath = '/fake/path';
app.state.setGenericDialogOptions = jest.fn();
app.state.setVisibleMosaics = jest.fn();
app.setEditorValues = jest.fn();
const editorValues = {
@@ -172,6 +174,7 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.state.setVisibleMosaics = jest.fn();
app.setEditorValues = jest.fn();
const editorValues = {
@@ -195,6 +198,7 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = true;
app.state.setVisibleMosaics = jest.fn();
app.state.setGenericDialogOptions = jest.fn();
app.setEditorValues = jest.fn();
@@ -226,6 +230,7 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = true;
app.state.setVisibleMosaics = jest.fn();
app.state.setGenericDialogOptions = jest.fn();
app.setEditorValues = jest.fn();
@@ -52,6 +52,17 @@ Array [
shouldDismissPopover={true}
text="Stylesheet (styles.css)"
/>,
<React.Fragment>
<Blueprint3.MenuDivider />
<Blueprint3.MenuItem
disabled={false}
icon="grid-view"
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Reset Layout"
/>
</React.Fragment>,
]
`;
@@ -112,6 +123,17 @@ exports[`EditorDropdown component renders 1`] = `
shouldDismissPopover={true}
text="Stylesheet (styles.css)"
/>
<React.Fragment>
<Blueprint3.MenuDivider />
<Blueprint3.MenuItem
disabled={false}
icon="grid-view"
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Reset Layout"
/>
</React.Fragment>
</Blueprint3.Menu>
}
defaultIsOpen={false}
@@ -203,6 +225,17 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
shouldDismissPopover={true}
text="Stylesheet (styles.css)"
/>
<React.Fragment>
<Blueprint3.MenuDivider />
<Blueprint3.MenuItem
disabled={false}
icon="grid-view"
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Reset Layout"
/>
</React.Fragment>
</Blueprint3.Menu>
}
defaultIsOpen={false}
@@ -1,63 +1,71 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Publish button component renders 1`] = `
<fieldset>
<Blueprint3.ButtonGroup
className="button-publish"
>
<Blueprint3.Popover
boundary="scrollParent"
captureDismiss={false}
content={
<Blueprint3.Menu>
<Blueprint3.MenuItem
active={false}
disabled={false}
icon="lock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Private"
/>
<Blueprint3.MenuItem
active={true}
disabled={false}
icon="unlock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Public"
/>
</Blueprint3.Menu>
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
inheritDarkTheme={true}
interactionKind="click"
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
position="bottom"
targetTagName="span"
transitionDuration={300}
usePortal={true}
wrapperTagName="span"
<Fragment>
<fieldset>
<Blueprint3.ButtonGroup
className="button-publish"
>
<Blueprint3.Popover
boundary="scrollParent"
captureDismiss={false}
content={
<Blueprint3.Menu>
<Blueprint3.MenuItem
active={false}
disabled={false}
icon="lock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Private"
/>
<Blueprint3.MenuItem
active={true}
disabled={false}
icon="unlock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Public"
/>
</Blueprint3.Menu>
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
inheritDarkTheme={true}
interactionKind="click"
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
position="bottom"
targetTagName="span"
transitionDuration={300}
usePortal={true}
wrapperTagName="span"
>
<Blueprint3.Button
icon="unlock"
/>
</Blueprint3.Popover>
<Blueprint3.Button
icon="unlock"
icon="upload"
onClick={[Function]}
text="Publish"
/>
</Blueprint3.Popover>
<Blueprint3.Button
icon="upload"
onClick={[Function]}
text="Publish"
/>
</Blueprint3.ButtonGroup>
</fieldset>
</Blueprint3.ButtonGroup>
</fieldset>
<Blueprint3.Toaster
autoFocus={false}
canEscapeKeyClear={true}
position="bottom-right"
usePortal={true}
/>
</Fragment>
`;
@@ -40,7 +40,7 @@ exports[`ExecutionSettings component renders 1`] = `
<p>
Electron allows starting the executable with
<a
href="https://electronjs.org/docs/api/chrome-command-line-switches"
href="https://www.electronjs.org/docs/api/command-line-switches"
>
user-provided flags
</a>
@@ -47,10 +47,10 @@ describe('Publish button component', () => {
const wrapper = shallow(<PublishButton appState={store} />);
const instance: PublishButton = wrapper.instance() as any;
instance.publishFiddle = jest.fn();
instance.publishOrUpdateFiddle = jest.fn();
await instance.handleClick();
expect(instance.publishFiddle).toHaveBeenCalled();
expect(instance.publishOrUpdateFiddle).toHaveBeenCalled();
});
it('attempts to publish to Gist', async () => {
@@ -66,7 +66,7 @@ describe('Publish button component', () => {
const wrapper = shallow(<PublishButton appState={store} />);
const instance: PublishButton = wrapper.instance() as any;
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
description: 'Electron Fiddle Gist',
@@ -96,7 +96,7 @@ describe('Publish button component', () => {
(window as any).ElectronFiddle.app.getEditorValues.mockReturnValueOnce({});
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
description: 'Electron Fiddle Gist',
@@ -126,7 +126,7 @@ describe('Publish button component', () => {
const wrapper = shallow(<PublishButton appState={store} />);
const instance: PublishButton = wrapper.instance() as any;
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
expect(store.isPublishing).toBe(false);
});
@@ -147,7 +147,7 @@ describe('Publish button component', () => {
const instance: PublishButton = wrapper.instance() as any;
instance.setPrivate();
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
...expectedGistCreateOpts,
@@ -155,7 +155,7 @@ describe('Publish button component', () => {
});
instance.setPublic();
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
expect(mockOctokit.gists.create).toHaveBeenCalledWith({
...expectedGistCreateOpts,
@@ -170,7 +170,7 @@ describe('Publish button component', () => {
expect(wrapper.find('fieldset').prop('disabled')).toBe(false);
instance.publishFiddle = jest.fn().mockImplementationOnce(() => {
instance.publishOrUpdateFiddle = jest.fn().mockImplementationOnce(() => {
return new Promise((resolve) => {
wrapper.setProps({ appState: { store, isPublishing: true } }, () => {
expect(wrapper.find('fieldset').prop('disabled')).toBe(true);
@@ -182,7 +182,7 @@ describe('Publish button component', () => {
});
});
await instance.publishFiddle();
await instance.publishOrUpdateFiddle();
});
describe('privacy menu', () => {
@@ -110,7 +110,7 @@ describe('BisectDialog component', () => {
const instance: BisectDialog = wrapper.instance() as any;
await instance.onSubmit();
expect(Bisector).toHaveBeenCalledWith(versions.slice(0, 5));
expect(Bisector).toHaveBeenCalledWith(versions.slice(0, 5).reverse());
expect(store.Bisector).toBeDefined();
expect(store.setVersion).toHaveBeenCalledWith(version);
});
@@ -181,6 +181,7 @@ describe('TokenDialog component', () => {
await instance.onSubmitToken();
expect(wrapper.state('error')).toBe(true);
expect(store.gitHubToken).toEqual(null);
});
});
});
+33 -4
View File
@@ -1,3 +1,5 @@
import * as MonacoType from 'monaco-editor';
import { ALL_MOSAICS, EditorId, GenericDialogType, PanelId, VersionSource, VersionState } from '../../src/interfaces';
import { getDownloadedVersions, getDownloadingVersions, removeBinary, setupBinary } from '../../src/renderer/binary';
import { Bisector } from '../../src/renderer/bisect';
@@ -511,10 +513,14 @@ describe('AppState', () => {
});
describe('setVisibleMosaics()', () => {
it('updates the visible editors and creates a backup', () => {
it('updates the visible editors and creates a backup', async () => {
appState.mosaicArrangement = createMosaicArrangement(ALL_MOSAICS);
appState.closedPanels = {};
appState.setVisibleMosaics([EditorId.main]);
await appState.setVisibleMosaics([EditorId.main]);
// we just need to mock something truthy here
// tslint:disable-next-line:no-object-literal-type-assertion
window.ElectronFiddle.editors[EditorId.main] = ({} as MonacoType.editor.IStandaloneCodeEditor);
expect(appState.mosaicArrangement).toEqual(EditorId.main);
expect(appState.closedPanels[EditorId.renderer]).toBeTruthy();
@@ -522,10 +528,17 @@ describe('AppState', () => {
expect(appState.closedPanels[EditorId.main]).toBeUndefined();
});
it('removes the backup for a non-editor right away', () => {
it('removes the backup for a non-editor right away', async () => {
appState.closedPanels = {};
appState.closedPanels[PanelId.docsDemo] = true;
appState.setVisibleMosaics(ALL_MOSAICS);
for (const mosaic of ALL_MOSAICS) {
// we just need to mock something truthy here
// tslint:disable-next-line:no-object-literal-type-assertion
window.ElectronFiddle.editors[mosaic] = ({} as MonacoType.editor.IStandaloneCodeEditor);
}
await appState.setVisibleMosaics(ALL_MOSAICS);
expect(appState.closedPanels[PanelId.docsDemo]).toBeUndefined();
});
@@ -560,4 +573,20 @@ describe('AppState', () => {
});
});
});
describe('resetEditorLayout()', () => {
it('Puts editors in default arrangement', () => {
appState.hideAndBackupMosaic(EditorId.main);
expect(appState.mosaicArrangement).toEqual({
direction: 'row',
first: EditorId.renderer,
second: EditorId.html
});
appState.resetEditorLayout();
expect(appState.mosaicArrangement).toEqual(DEFAULT_MOSAIC_ARRANGEMENT);
});
});
});
+2 -2
View File
@@ -16,11 +16,11 @@ describe('getEditorModel()', () => {
fiddle.editors[EditorId.html] = oldEditor;
});
it('throws if window.Fiddle is not ready', () => {
it('returns null if window.Fiddle is not ready', () => {
const { ElectronFiddle: fiddle } = window as any;
(window as any).ElectronFiddle = undefined;
expect(() => getEditorModel(EditorId.html)).toThrow('Fiddle not ready');
expect(getEditorModel(EditorId.html)).toEqual(null);
window.ElectronFiddle = fiddle;
});
+23
View File
@@ -0,0 +1,23 @@
import { waitForEditorsToMount } from '../../src/utils/editor-mounted';
import { EditorId } from '../../src/interfaces';
describe('waitforEditorsToMount', () => {
it('resolves when editors match list', async () => {
// these editors should be mounted by default.
const { ElectronFiddle: fiddle } = window as any;
await waitForEditorsToMount([EditorId.html, EditorId.main, EditorId.renderer]);
expect(fiddle.editors).toHaveProperty(EditorId.html);
expect(fiddle.editors).toHaveProperty(EditorId.main);
expect(fiddle.editors).toHaveProperty(EditorId.renderer);
});
it('rejects if editors fail to match list', async () => {
try {
// css editor shouldn't be mounted by default.
await waitForEditorsToMount([EditorId.css]);
} catch (error) {
expect(error).toMatch('Timed out');
}
});
});
+26 -3
View File
@@ -1,16 +1,39 @@
import { EditorId } from '../../src/interfaces';
import { getEditorValue } from '../../src/utils/editor-value';
import { MockState } from '../mocks/state';
describe('getEditorValue()', () => {
it('returns the value for an editor', () => {
it('returns the value for an editor if it exists', () => {
expect(getEditorValue(EditorId.html)).toBe('editor-value');
});
it('throws if window.Fiddle is not ready', () => {
it('returns the value for the editor backup if it exists', () => {
// set up mock state that has the editor deleted and a backup
const oldEditor = window.ElectronFiddle.editors[EditorId.html];
window.ElectronFiddle.editors[EditorId.html] = null;
window.ElectronFiddle.app.state = (new MockState() as any);
const mockState = window.ElectronFiddle.app.state;
mockState.closedPanels = {
[EditorId.html]: {
value: 'editor-backup-value'
}
};
// assert
expect(getEditorValue(EditorId.html)).toBe('editor-backup-value');
// revert to initial state
delete window.ElectronFiddle.app.state;
window.ElectronFiddle.editors[EditorId.html] = oldEditor;
});
it('returns an empty string if window.Fiddle is not ready', () => {
const oldFiddle = window.ElectronFiddle;
(window as any).ElectronFiddle = undefined;
expect(() => getEditorValue(EditorId.html)).toThrow('Fiddle not ready');
expect(getEditorValue(EditorId.html)).toBe('');
window.ElectronFiddle = oldFiddle;
});
+1 -1
View File
@@ -20,7 +20,7 @@ describe('getEditorViewState()', () => {
const { ElectronFiddle: fiddle } = window as any;
(window as any).ElectronFiddle = undefined;
expect(() => getEditorViewState(EditorId.html)).toThrow('Fiddle not ready');
expect(getEditorViewState(EditorId.html)).toBeNull();
window.ElectronFiddle = fiddle;
});