diff --git a/app/components/file-editor-column.js b/app/components/file-editor-column.js
index 8aac2586..62ed54ae 100644
--- a/app/components/file-editor-column.js
+++ b/app/components/file-editor-column.js
@@ -1,5 +1,8 @@
import Ember from 'ember';
+const { computed } = Ember;
+const MAX_COLUMNS = 3;
+
export default Ember.Component.extend({
focusEditor: 'focusEditor',
selectFile: 'selectFile',
@@ -18,6 +21,11 @@ export default Ember.Component.extend({
}
}),
+ isLastColumn: computed('col', 'numColumns', function() {
+ let numColumns = this.get('numColumns');
+ return (this.get('col') | 0) === numColumns && numColumns < MAX_COLUMNS;
+ }),
+
focusIn () {
this.sendAction('focusEditor', this);
},
@@ -30,6 +38,14 @@ export default Ember.Component.extend({
valueUpdated() {
this.sendAction('contentsChanged');
+ },
+
+ removeColumn(col) {
+ this.attrs.removeColumn(col);
+ },
+
+ addColumn() {
+ this.attrs.addColumn();
}
}
});
diff --git a/app/gist/controller.js b/app/gist/controller.js
index 13de0785..b4f2cd0b 100644
--- a/app/gist/controller.js
+++ b/app/gist/controller.js
@@ -2,18 +2,25 @@ import Ember from "ember";
import config from '../config/environment';
import Settings from '../models/settings';
import ErrorMessages from 'ember-twiddle/helpers/error-messages';
+import Column from '../utils/column';
const {
- computed: { equal },
- observer,
+ computed,
run
} = Ember;
+const MAX_COLUMNS = 3;
+
export default Ember.Controller.extend({
emberCli: Ember.inject.service('ember-cli'),
version: config.APP.version,
+
+ queryParams: ['numColumns'],
+ numColumns: 2,
+
init() {
this._super(...arguments);
+ this.createColumns();
this.setupWindowUpdate();
},
@@ -22,14 +29,32 @@ export default Ember.Controller.extend({
* @type {String}
*/
buildOutput: '',
+
+ /**
+ * If the code is currently being built
+ * @type {boolean}
+ */
isBuilding: false,
+
+ /**
+ * If the edited code has not been saved by the user
+ * @type {boolean}
+ */
unsaved: true,
+
+ /**
+ * File in the current active editor column
+ * @type {Object}
+ */
activeFile: null,
+
+ /**
+ * Column which has the currently focused editor
+ * @type {Number}
+ */
activeEditorCol: null,
- col1File: null,
- col2File: null,
- col1Active: equal('activeEditorCol','1'),
- col2Active: equal('activeEditorCol','2'),
+
+ columns: null,
settings: Settings.create(),
@@ -69,20 +94,69 @@ export default Ember.Controller.extend({
});
},
+ realNumColumns: computed('numColumns', function() {
+ return Math.min(this.get('numColumns'), MAX_COLUMNS);
+ }),
+ noColumns: computed.equal('numColumns', 0),
+
/**
- * Set the initial file columns
+ * Creates the column objects
*/
- initializeColumns: observer('model', function() {
- var files = this.get('model.files');
+ createColumns() {
+ let columns = [];
+ for (let i = 0; i < MAX_COLUMNS; ++i) {
+ let col = (i + 1) + "";
+ columns.pushObject(Column.create({
+ col: col,
+ controller: this
+ }));
+ }
+ this.set('columns', columns);
+ },
- if(files.objectAt(0)) {
- this.setColumnFile(1, files.objectAt(0));
+ /**
+ * Set the initial files in the columns
+ */
+ initializeColumns() {
+ let files = this.get('model.files');
+ let numColumns = this.get('realNumColumns');
+
+ let j = 0;
+ for (let i = 1; i <= numColumns; ++i) {
+ if (!this.getColumnFile(i)) {
+ if (files) {
+ j = 0;
+ while (!this.isOpen(files.objectAt(j))) {
+ j++;
+ }
+ let file = files.objectAt(j);
+ if (file) {
+ this.setColumnFile(i, file);
+ }
+ }
+ }
}
+ },
- if(files.objectAt(1)) {
- this.setColumnFile(2, files.objectAt(1));
+ /**
+ * Returns true if the passed in file is currently open
+ * @param {Object} one of the files in the gist
+ * @return {boolean}
+ */
+ isOpen(file) {
+ if (!file) {
+ return false;
+ }
+
+ for (let i = 1; i <= MAX_COLUMNS; ++i) {
+ let colFile = this.getColumnFile(i);
+ if (colFile && colFile.get('fileName') === file.get('fileName')){
+ return false;
+ }
}
- }),
+
+ return true;
+ },
rebuildApp: function() {
if (this.get('isLiveReload')) {
@@ -107,6 +181,7 @@ export default Ember.Controller.extend({
this.send('contentsChanged');
}
},
+
/*
* Test whether path is valid. Presently only tests whether components are hyphenated.
*/
@@ -123,9 +198,13 @@ export default Ember.Controller.extend({
}
return false;
},
- setColumnFile(column, file){
- let fileColumn = `col${column}File`;
- this.set(fileColumn, file);
+
+ getColumnFile(column) {
+ return this.get('columns').objectAt(column - 1).get('file');
+ },
+
+ setColumnFile(column, file) {
+ this.get('columns').objectAt(column - 1).set('file', file);
},
actions: {
@@ -179,6 +258,7 @@ export default Ember.Controller.extend({
this.createFile(filePath, fileProperties, fileColumn);
});
},
+
/**
* Add a new file to the model
* @param {String|null} type Blueprint name or null for empty file
@@ -225,6 +305,32 @@ export default Ember.Controller.extend({
}
},
+ removeColumn (col) {
+ let numColumns = this.get('realNumColumns');
+
+ for (var i = (col|0); i < numColumns; ++i) {
+ this.setColumnFile(i, this.getColumnFile(i + 1));
+ }
+ this.setColumnFile(numColumns, undefined);
+
+ let activeCol = this.get('activeEditorCol');
+ if (activeCol >= col) {
+ this.set('activeEditorCol', ((activeCol|0) - 1).toString());
+ }
+
+ this.transitionToRoute({queryParams: {numColumns: numColumns - 1}});
+ },
+
+ addColumn() {
+ let numColumns = this.get('realNumColumns');
+
+ this.transitionToRoute({
+ queryParams: {
+ numColumns: numColumns + 1
+ }
+ }).then(this.initializeColumns.bind(this));
+ },
+
setEditorKeyMap (keyMap) {
const settings = this.get('settings');
settings.set('keyMap', keyMap);
@@ -233,11 +339,10 @@ export default Ember.Controller.extend({
},
_removeFileFromColumns (file) {
- if(this.get('col1File') === file) {
- this.setColumnFile(1, null);
- }
- if(this.get('col2File') === file) {
- this.setColumnFile(2, null);
+ for (let i = 1; i <= MAX_COLUMNS; ++i) {
+ if (this.getColumnFile(i) === file) {
+ this.setColumnFile(i, null);
+ }
}
},
diff --git a/app/gist/template.hbs b/app/gist/template.hbs
index 4b3348fc..dc1d46ce 100644
--- a/app/gist/template.hbs
+++ b/app/gist/template.hbs
@@ -17,9 +17,7 @@
- {{!-- {{#if isEditingDescription}} --}}
{{title-input value=model.description}}
- {{!-- {{model.description}} --}}
{{saved-state-indicator model=model
unsaved=unsaved
}}
@@ -33,17 +31,27 @@
-
-
- {{file-editor-column col="1" file=col1File allFiles=model.files keyMap=settings.keyMap contentsChanged="contentsChanged"}}
-
-
-
- {{file-editor-column col="2" file=col2File allFiles=model.files keyMap=settings.keyMap contentsChanged="contentsChanged"}}
-
+ {{#each columns as |column|}}
+ {{#if column.show}}
+
+ {{file-editor-column col=column.col
+ file=column.file
+ allFiles=model.files
+ keyMap=settings.keyMap
+ numColumns=realNumColumns
+ contentsChanged="contentsChanged"
+ removeColumn=(action "removeColumn")
+ addColumn=(action "addColumn")
+ }}
+
+ {{/if}}
+ {{/each}}
{{#if file}}
diff --git a/app/utils/column.js b/app/utils/column.js
new file mode 100644
index 00000000..4f52f20f
--- /dev/null
+++ b/app/utils/column.js
@@ -0,0 +1,15 @@
+import Ember from "ember";
+
+const { computed } = Ember;
+
+export default Ember.Object.extend ({
+ col: "",
+ controller: null,
+ active: computed('controller.activeEditorCol', 'col', function() {
+ return this.get('controller.activeEditorCol') === this.get('col');
+ }),
+ show: computed('controller.realNumColumns', 'col', function() {
+ return this.get('controller.realNumColumns') >= this.get('col');
+ }),
+ file: null
+});
diff --git a/tests/acceptance/columns-test.js b/tests/acceptance/columns-test.js
new file mode 100644
index 00000000..d5532e55
--- /dev/null
+++ b/tests/acceptance/columns-test.js
@@ -0,0 +1,65 @@
+import Ember from 'ember';
+import { module, test } from 'qunit';
+import startApp from 'ember-twiddle/tests/helpers/start-app';
+
+module('Acceptance | columns', {
+ beforeEach: function() {
+ this.application = startApp();
+
+ server.create('user', 'octocat');
+ },
+
+ afterEach: function() {
+ Ember.run(this.application, 'destroy');
+ }
+});
+
+const columns = ".code";
+const firstColumn = ".code:first-of-type";
+const plusGlyph = ".code .glyphicon-plus";
+const removeGlyph = firstColumn + " .glyphicon-remove";
+const outputPlusGlyph = ".output .glyphicon-plus";
+
+test('you can add and remove columns', function(assert) {
+ visit('/');
+
+ andThen(function() {
+ assert.equal(currentURL(), '/', 'We are on the correct initial route');
+ assert.equal(find(columns).length, 2, 'There are two columns to start');
+
+ find(plusGlyph).click();
+ });
+
+ andThen(function() {
+ assert.equal(currentURL(), '/?numColumns=3', 'We are on the correct route for 3 columns');
+ assert.equal(find(columns).length, 3, 'There are now 3 columns');
+
+ find(removeGlyph).click();
+ });
+
+ andThen(function() {
+ assert.equal(currentURL(), '/', 'We are on the correct route for 2 columns');
+ assert.equal(find(columns).length, 2, 'There are now 2 columns');
+
+ find(removeGlyph).click();
+ });
+
+ andThen(function() {
+ assert.equal(currentURL(), '/?numColumns=1', 'We are on the correct route for 1 columns');
+ assert.equal(find(columns).length, 1, 'There are now 1 columns');
+
+ find(removeGlyph).click();
+ });
+
+ andThen(function() {
+ assert.equal(currentURL(), '/?numColumns=0', 'We are on the correct route for 0 columns');
+ assert.equal(find(columns).length, 0, 'There are now 0 columns');
+
+ find(outputPlusGlyph).click();
+ });
+
+ andThen(function() {
+ assert.equal(currentURL(), '/?numColumns=1', 'We are on the correct route for 1 columns');
+ assert.equal(find(columns).length, 1, 'There are now 1 columns');
+ });
+});
diff --git a/tests/integration/components/file-editor-column-test.js b/tests/integration/components/file-editor-column-test.js
new file mode 100644
index 00000000..6fee347b
--- /dev/null
+++ b/tests/integration/components/file-editor-column-test.js
@@ -0,0 +1,39 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('file-editor-column', 'Integration | Component | file editor column', {
+ integration: true,
+ beforeEach() {
+ this.addColumnCalled = false;
+ this.removeColumnCalled = false;
+
+ this.on('addColumn', () => { this.addColumnCalled = true; });
+ this.on('removeColumn', () => { this.removeColumnCalled = true; });
+
+ this.render(hbs`{{file-editor-column col="2"
+ file=null
+ allFiles=null
+ keyMap=null
+ numColumns=2
+ contentsChanged="contentsChanged"
+ addColumn=(action "addColumn")
+ removeColumn=(action "removeColumn")
+ }}`);
+ }
+});
+
+test('it calls addColumn when the add column glyph is clicked', function(assert) {
+ assert.expect(1);
+
+ this.$('.glyphicon-plus').click();
+
+ assert.ok(this.addColumnCalled, 'addColumn was called');
+});
+
+test('it calls removeColumn when the remove column glyph is clicked', function(assert) {
+ assert.expect(1);
+
+ this.$('.glyphicon-remove').click();
+
+ assert.ok(this.removeColumnCalled, 'removeColumn was called');
+});
diff --git a/tests/unit/utils/column-test.js b/tests/unit/utils/column-test.js
new file mode 100644
index 00000000..572e2bb9
--- /dev/null
+++ b/tests/unit/utils/column-test.js
@@ -0,0 +1,31 @@
+import Ember from "ember";
+import Column from '../../../utils/column';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | column');
+
+test('Active Column matches', function(assert) {
+ assert.expect(2);
+ var controller = Ember.Object.create({
+ activeEditorCol: '2',
+ realNumColumns: 3
+ });
+ var column1 = Column.create({ col: '1', controller: controller });
+ assert.ok(!column1.get('active'));
+ var column2 = Column.create({ col: '2', controller: controller });
+ assert.ok(column2.get('active'));
+});
+
+test('Show Column if less than or equal to realNumColumns', function(assert) {
+ assert.expect(3);
+ var controller = Ember.Object.create({
+ activeEditorCol: '3',
+ realNumColumns: 2
+ });
+ var column1 = Column.create({ col: '1', controller: controller });
+ assert.ok(column1.get('show'));
+ var column2 = Column.create({ col: '2', controller: controller });
+ assert.ok(column2.get('show'));
+ var column3 = Column.create({ col: '3', controller: controller });
+ assert.ok(!column3.get('show'));
+});