Skip to content
This repository was archived by the owner on Jul 30, 2018. It is now read-only.

Commit 1a063c2

Browse files
authored
Convert Runner and Editor to widgets (#14)
Fixes #6
1 parent 3286d9f commit 1a063c2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1239
-973
lines changed

Gruntfile.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ require('ts-node')
33
'compilerOptions': {
44
module: 'commonjs',
55
target: 'es2017',
6+
lib: [
7+
'es5',
8+
'es2015',
9+
'es2016',
10+
'es2017'
11+
],
612
typeRoots: [
713
'node_modules/@types'
814
],

README.md

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The `web-editor` provides three main errors of functionality:
1818
* Provide an abstraction API to allow editing of those project files in the [`monaco-editor`](https://github.com/Microsoft/monaco-editor)
1919
in a browser.
2020
* Provide an API to allow the loaded project to be run in an `iframe`.
21+
* A router which understands GitHub gists ID for being able to load a project from a gist.
2122

2223
This package is inteded to be integrated into a website which would provide a more rich user interface to allow editing and running
2324
the project in a browser. There is a minimal API implemented in `examples/index.html` which allows the loading of example projects
@@ -47,6 +48,8 @@ There are several key methods of `project`:
4748
in the project, but you can specify as many different file types as arguments and only those types of files will be returned.
4849
* `.get(): ProjectJson | undefined` - Returns the object representation of the project JSON, including any changes that have been made
4950
while the project has been loaded.
51+
* `.getProgram(): Promise<Program>` - Returns an object which contains the properties of `css`, `dependencies`, `html`, and `modules`
52+
which are designed to be used with the `Runner` widget as widget properties to specify the program to run.
5053

5154
#### Usage
5255

@@ -63,65 +66,97 @@ import project from '@dojo/web-editor/project';
6366

6467
### Editor
6568

66-
This is a class which wraps `monaco-editor`, allowing the editor to seemlessly integrate with the project. It will automatically
69+
This is a widget which wraps `monaco-editor`, allowing the editor to seemlessly integrate with the project. It will automatically
6770
display files with the appropriate language intellisense as well as the file is edited, changes will automatically be sent to
6871
the project to keep it updated.
6972

70-
The `Editor` constructor takes two arguments. First, you need to provide a DOM element that will serve as the root for the editor.
71-
The second is an optional argument is a pass through of any options as specified in the `monaco.editor.IEditorOptions` interface.
73+
The `Editor` has two key properties:
7274

73-
The class has only one method:
75+
* `filename` - This is the name of the file in the project which the editor should be displaying. Changing this property will cause the widget to change the file that is currently being displayed.
76+
* `options` - Passed to the `monaco-editor` when it is created. Needs to conform to the `monaco.editor.IEditorOptions` interface.
7477

75-
* `.display(filename: string): void` - Displays the file based on the name supplied. It will throw if the file is not part of the
76-
currently loaded project.
78+
`Editor` is a themeable widget, with the only themeable class being `editor.base`.
7779

7880
#### Usage
7981

8082
Typical usage would be something like this:
8183

82-
```typescript
84+
```ts
8385
import project from '@dojo/web-editor/project';
8486
import Editor from '@dojo/web-editor/Editor';
87+
import { v, w } from '@dojo/widget-core/d';
88+
import Projector from '@dojo/widget-core/mixins/Projector';
89+
import WidgetBase from '@dojo/widget-core/WidgetBase';
8590

8691
(async () => {
8792
await project.load('some.project.json');
88-
const editor = new Editor(document.getElementById('editor'));
89-
editor.display('./src/somefile.ts');
93+
94+
class App extends WidgetBase {
95+
render() {
96+
return v('div', [
97+
w(Editor, {
98+
filename: './src/main.ts'
99+
})
100+
]);
101+
}
102+
}
103+
104+
const projector = new (Projector(App))();
105+
projector.append();
90106
})();
91107
```
92108

93109
### Runner
94110

95-
This is a class which provides a simple API to run instances of a project within an `iframe` on a page. It will automatically
96-
transpile the project and send the transpiled output to the `iframe`.
111+
A widget _runs_ a program in an `iframe`. Updating the properties related to the program on the widget will
112+
cause it to re-render the program.
97113

98-
The `Runner` constructor takes a single arugment, which is the `iframe` it should use to run the project in.
114+
The `Runner` has several key properties which control its behaviour:
99115

100-
The class has one method of note:
116+
* `css`, `dependencies`, `html`, and `modules` - Properties that are part of a `Program` obtained from the returned promise of
117+
`project.getProgram()`.
118+
* `loader` - An optional property which specifies the URI for the AMD loader to use with the program. It defaults to the latest
119+
version of the `@dojo/loader`.
120+
* `src` - An optional URI that will be the `src` of the `iframe` until a program is first run. This needs to be a relative URI
121+
to the host in order to ensure that the run program is not impacted by cross-domain restrictions.
122+
* `onError` - When a program is running and generates an error, this method would be called, passing a single argument of the error.
123+
* `onInitIframe` - Called, with an argument of the `iframe` which is called when the `Runner` has configured the `iframe` in the DOM
124+
* `onRun` - A method that is called when the `Runner` is finished standing up the program. It does not reflect the actual state
125+
of the program.
101126

102-
* `.run(): Promise<void>` - an async function which will resolve when the project has been run.
127+
`Runner` is a themeable widget, with the only themeable class being `runner.base`.
103128

104129
#### Usage
105130

106-
```html
107-
<!DOCTYPE html>
108-
<html>
109-
<head><title>Example</title></head>
110-
<body>
111-
<iframe src="@dojo/web-editor/support/blank.html" id="runner"></iframe>
112-
</body>
113-
</html>
114-
```
115-
116-
```typescript
117-
import project from '@dojo/web-editor/project';
118-
import Runner from '@dojo/web-editor/Runner';
131+
```ts
132+
import { assign } from '@dojo/core/lang';
133+
import project, { Program } from '@dojo/web-editor/project';
134+
import Runner, { RunnerProperties } from '@dojo/web-editor/Runner';
135+
import { v, w } from '@dojo/widget-core/d';
136+
import { WidgetProperties } from '@dojo/widget-core/interfaces';
137+
import Projector from '@dojo/widget-core/mixins/Projector';
138+
import WidgetBase from '@dojo/widget-core/WidgetBase';
139+
140+
interface AppProperties extends WidgetProperties {
141+
program?: Program
142+
}
119143

120144
(async () => {
121145
await project.load('some.project.json');
122-
const runner = new Runner(document.getElementById('runner'));
123-
await runner.run();
124-
console.log('Ran!');
146+
147+
class App extends WidgetBase<AppProperties> {
148+
render() {
149+
return v('div', [
150+
w(Runner, this.properties.program)
151+
]);
152+
}
153+
}
154+
155+
const projector = new (Projector(App))();
156+
projector.append();
157+
158+
const program = await project.getProgram();
159+
project.setProperties({ program });
125160
})();
126161
```
127162

package.json

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"homepage": "https://github.com/dojo/web-editor#readme",
1919
"devDependencies": {
2020
"@dojo/cli-export-project": ">=2.0.0-alpha.1",
21-
"@dojo/i18n": "beta1",
22-
"@dojo/interfaces": "beta1",
23-
"@dojo/test-extras": ">=2.0.0-alpha.1",
24-
"@dojo/widget-core": "beta1",
21+
"@dojo/i18n": "next",
22+
"@dojo/interfaces": "next",
23+
"@dojo/test-extras": "^2.0.0-alpha.4",
2524
"@types/glob": "^5.0.30",
2625
"@types/grunt": "^0.4.21",
26+
"@types/jsdom": "^2.0.30",
2727
"@types/node": "^7.0.12",
2828
"@types/sinon": "^2.1.2",
2929
"@types/source-map": "^0.5.0",
@@ -37,28 +37,32 @@
3737
"grunt-contrib-clean": "^1.0.0",
3838
"grunt-contrib-copy": "^1.0.0",
3939
"grunt-patcher": "^1.0.0",
40+
"grunt-postcss": "^0.8.0",
4041
"grunt-ts": "^6.0.0-beta.15",
4142
"grunt-tslint": "^5.0.1",
4243
"grunt-webpack": "^2.0.1",
4344
"intern": "^3.4.3",
4445
"istanbul": "^0.4.5",
45-
"maquette": "2.4.3",
4646
"postcss": "^5.2.17",
4747
"postcss-cssnext": "^2.10.0",
48+
"postcss-import": "^10.0.0",
4849
"postcss-modules": "^0.6.4",
4950
"remap-istanbul": "^0.9.5",
5051
"sinon": "^2.1.0",
5152
"ts-node": "^3.0.2",
52-
"tslint": "^5.0.0",
53+
"tslint": "^5.3.2",
54+
"typed-css-modules": "^0.2.0",
5355
"typescript": "^2.3.1",
5456
"webpack": "^2.4.1"
5557
},
5658
"dependencies": {
57-
"@dojo/core": "beta1",
58-
"@dojo/has": "beta1",
59-
"@dojo/loader": "beta1",
60-
"@dojo/routing": "beta1",
61-
"@dojo/shim": "beta1",
59+
"@dojo/core": "next",
60+
"@dojo/has": "next",
61+
"@dojo/loader": "next",
62+
"@dojo/routing": "next",
63+
"@dojo/shim": "next",
64+
"@dojo/widget-core": "next",
65+
"maquette": "kitsonk/maquette#build-master",
6266
"monaco-editor": "^0.8.3",
6367
"source-map": "^0.5.6"
6468
}

src/Editor.ts

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,96 @@
1-
import Destroyable from '@dojo/core/Destroyable';
21
import global from '@dojo/core/global';
32
import { createHandle } from '@dojo/core/lang';
3+
import { queueTask } from '@dojo/core/queue';
44
import { debounce } from '@dojo/core/util';
5+
import { v, w } from '@dojo/widget-core/d';
6+
import { Constructor, VirtualDomProperties, WidgetProperties } from '@dojo/widget-core/interfaces';
7+
import WidgetBase from '@dojo/widget-core/WidgetBase';
8+
import { ThemeableMixin, ThemeableProperties, theme } from '@dojo/widget-core/mixins/Themeable';
9+
import DomWrapper from '@dojo/widget-core/util/DomWrapper';
510
import project from './project';
11+
import * as css from './styles/editor.m.css';
612

713
const globalMonaco: typeof monaco = global.monaco;
814

915
/**
10-
* A class which is a simple abstraction of the `monaco-editor` editor
16+
* @type EditorProperties
17+
*
18+
* Properties that can be set on an `Editor` widget
19+
*
20+
* @property filename The filename (from the current `project`) that the editor should be displaying for editing
21+
* @property options Editor options that should be passed to the monaco editor when it is created
22+
* @property onEditorInit Called when the monaco editor is created and initialized by the widget, passing the instance of the monaco editor
23+
* @property onEditorLayout Called when the widget calls `.layout()` on the monaco editor
1124
*/
12-
export default class Editor extends Destroyable {
13-
private _currentFile: string;
14-
private _editor: monaco.editor.IStandaloneCodeEditor;
25+
export interface EditorProperties extends WidgetProperties, ThemeableProperties {
26+
filename?: string;
27+
options?: monaco.editor.IEditorOptions;
1528

29+
onEditorInit?(editor: monaco.editor.IStandaloneCodeEditor): void;
30+
onEditorLayout?(): void;
31+
}
32+
33+
const EditorBase = ThemeableMixin(WidgetBase);
34+
35+
@theme(css)
36+
export default class Editor extends EditorBase<EditorProperties> {
37+
private _editor: monaco.editor.IStandaloneCodeEditor | undefined;
38+
private _EditorDom: Constructor<WidgetBase<VirtualDomProperties & WidgetProperties>>;
39+
private _didChangeHandle: monaco.IDisposable;
40+
private _onAfterRender = () => {
41+
if (!this._editor) {
42+
this._editor = globalMonaco.editor.create(this._root, this.properties.options);
43+
this._didChangeHandle = this._editor.onDidChangeModelContent(debounce(this._onDidChangeModelContent, 1000));
44+
const { onEditorInit } = this.properties;
45+
this._setModel();
46+
onEditorInit && onEditorInit(this._editor);
47+
48+
this.own(createHandle(() => {
49+
if (this._editor) {
50+
this._editor.dispose();
51+
this._didChangeHandle.dispose();
52+
}
53+
}));
54+
}
55+
this._editor.layout();
56+
this._queuedLayout = false;
57+
const { onEditorLayout } = this.properties;
58+
onEditorLayout && onEditorLayout();
59+
}
1660
private _onDidChangeModelContent = () => {
17-
project.setFileDirty(this._currentFile);
61+
if (this.properties.filename) {
62+
project.setFileDirty(this.properties.filename);
63+
}
1864
}
65+
private _queuedLayout = false;
66+
private _root: HTMLDivElement;
1967

20-
/**
21-
* A class which is a simple abstraction of the `monaco-editor` editor
22-
* @param element The root HTML element to attach the editor to
23-
*/
24-
constructor (element: HTMLElement, options?: monaco.editor.IEditorOptions) {
25-
super();
26-
27-
this._editor = globalMonaco.editor.create(element, options);
28-
const didChangeHandle = this._editor.onDidChangeModelContent(debounce(this._onDidChangeModelContent, 1000));
68+
private _setModel() {
69+
const { filename } = this.properties;
70+
if (this._editor && filename && project.includes(filename)) {
71+
this._editor.setModel(project.getFileModel(filename));
72+
}
73+
}
2974

30-
this.own(createHandle(() => {
31-
this._editor.dispose();
32-
didChangeHandle.dispose();
33-
}));
75+
constructor() {
76+
super();
77+
const root = this._root = document.createElement('div');
78+
root.style.height = '100%';
79+
root.style.width = '100%';
80+
this._EditorDom = DomWrapper(root);
3481
}
3582

36-
/**
37-
* Display a file in the editor from the currently loaded project
38-
* @param model The model to display
39-
*/
40-
display(filename: string): void {
41-
if (!project.includes(filename)) {
42-
throw new Error(`File "${filename}" is not part of the project.`);
83+
public render() {
84+
/* TODO: Refactor when https://github.com/dojo/widget-core/pull/548 published */
85+
if (!this._queuedLayout) {
86+
/* doing this async, during the next major task, to allow the widget to actually render */
87+
this._queuedLayout = true;
88+
queueTask(this._onAfterRender);
4389
}
44-
this._currentFile = filename;
45-
this._editor.setModel(project.getFileModel(filename));
90+
this._setModel();
91+
/* TODO: Create single node when https://github.com/dojo/widget-core/issues/553 resolved */
92+
return v('div', {
93+
classes: this.classes(css.base)
94+
}, [ w(this._EditorDom, { key: 'editor' }) ]);
4695
}
4796
}

0 commit comments

Comments
 (0)