From ea26946ff81386d78c76810895b5cc21c95c9115 Mon Sep 17 00:00:00 2001
From: benmcmahon100 <benmcmahon100@gmail.com>
Date: Wed, 30 Dec 2015 22:34:49 +0000
Subject: [PATCH] Tabbed Editor and Custom Code Mirror sizes implemented. Misc
 tab has the odd behaviour of having all the un-rendered code mirrors instead
 of the ones that aren't defined in the layout. These should be avoided as
 they won't update the seed data on a save. Also added a nice font to the
 other elements

---
 src/Components/Editor.js              |  77 +++++++++++++++++++++--
 src/Components/GrandCentralStation.js | 115 +++++++++++++++++++---------------
 src/style.css                         |  17 +++++
 views/layout.jade                     |   1 +
 4 files changed, 157 insertions(+), 53 deletions(-)

diff --git a/src/Components/Editor.js b/src/Components/Editor.js
index 1bd8671..ec84d96 100644
--- a/src/Components/Editor.js
+++ b/src/Components/Editor.js
@@ -3,6 +3,7 @@ import './../../node_modules/codemirror/theme/monokai.css';
 import './../../node_modules/codemirror/addon/scroll/simplescrollbars.css';
 
 import React, {Component} from 'react';
+import $ from 'jquery';
 
 import {updateChallenge} from './../actions/editorActions';
 
@@ -57,6 +58,18 @@ class Editor extends Component {
       return (field[0] !== 'id');
     });
 
+    this.editorLayout = this.props.editorLayout;
+
+    let layout = {
+
+    };
+
+    for(var header in this.editorLayout){
+      layout[header] = [];
+    }
+
+    let finalHTML = [];
+
     unrenderedCodeMirrors = codeMirrorData.map(function(data) {
       if (Array.isArray(data[1])) {
         if (data[0] === 'tests') {
@@ -75,18 +88,48 @@ class Editor extends Component {
       );
     });
 
+    for(var i = 0; i < unrenderedCodeMirrors.length; i++){
+      let unrenderedCodeMirror = unrenderedCodeMirrors[i];
+      for(var j in this.editorLayout){
+        if(this.editorLayout[j].map((head) => {return(head.name)}).indexOf(unrenderedCodeMirror.key) > -1){
+          if(layout[j].indexOf(unrenderedCodeMirror) < 0) {
+            layout[j].push(unrenderedCodeMirror);
+          }
+        }
+        else {
+          if(layout['misc'].indexOf(unrenderedCodeMirror) < 0 && layout[j].indexOf(unrenderedCodeMirror) < 0) {
+            layout['misc'].push(unrenderedCodeMirror);
+          }
+        }
+      }
+    }
+
+    for(var k in layout){
+      finalHTML.push(
+        <div className = {"tab " + k} data-tab = {k} key = {k}>
+          {layout[k]}
+        </div>
+      );
+    }
+
     this.state = {
       codeMirrorData: codeMirrorData,
-      unrenderedCodeMirrors: unrenderedCodeMirrors
+      unrenderedCodeMirrors: finalHTML
     };
   }
 
+  toggleTabs(e) {
+    $('.tab').hide();
+    $('.' + e.target.dataset.tab).show();
+  }
+
   componentDidMount() {
     let codeMirrors = [];
     const dispatch = this.props.dispatch;
     const challengeId = this.props.challenge.id;
     const activeFile = this.props.activeFile;
     const challengeType = this.props.challenge.challengeType;
+    const editorLayout = this.editorLayout;
 
     this.state.codeMirrorData.map(function(codeMirror) {
       // Determine mode
@@ -108,7 +151,15 @@ class Editor extends Component {
           mode = 'htmlmixed';
           break;
       }
+
+      let cHeader = "";
+
       /* eslint-enable no-fallthrough */
+      for(var header in editorLayout){
+        if(editorLayout[header].map((field) => {return(field.name);}).indexOf(codeMirror[0]) > -1){
+          cHeader = header;
+        }
+      }
 
       let editor = CodeMirror.fromTextArea(
         document.getElementById(codeMirror[0]),
@@ -125,6 +176,10 @@ class Editor extends Component {
         }
       );
 
+      let cData = editorLayout[cHeader].filter((data) => {return(data.name === codeMirror[0]);})[0];
+
+      editor.setSize(cData.dimens[1], cData.dimens[0]);
+
       editor.on('change', function(instance) {
         updateChallenge(dispatch,
           {
@@ -137,15 +192,29 @@ class Editor extends Component {
           }
         );
       });
-
       codeMirrors.push(editor);
     });
+    $('.tab').hide();
+    $('.meta').show();
   }
 
   render() {
+    let tabButtons = [];
+    for(var tab in this.editorLayout){
+      tabButtons.push(
+        <div key = {tab} onClick = {this.toggleTabs} data-tab = {tab} className = "tabSelector">
+          {tab.split("_").join(" ")}
+        </div>
+      );
+    }
     return (
-      <div>
-        {this.state.unrenderedCodeMirrors}
+      <div className = "editorContainer">
+        <div className = "selectors">
+          {tabButtons}
+        </div>
+        <div className = "tabContainer">
+          {this.state.unrenderedCodeMirrors}
+        </div>
       </div>
     );
   }
diff --git a/src/Components/GrandCentralStation.js b/src/Components/GrandCentralStation.js
index 6d3493a..abab319 100644
--- a/src/Components/GrandCentralStation.js
+++ b/src/Components/GrandCentralStation.js
@@ -57,6 +57,67 @@ class GrandCentralStation extends Component {
     this.forceOpenNav = this.forceOpenNav.bind(this);
     this.handleSnackbar = this.handleSnackbar.bind(this);
     this.handleKeyboardSave = this.handleKeyboardSave.bind(this);
+
+    this.challengeSkeleton = {
+      id: "",
+      title: "",
+      description: [],
+      head: [],
+      challengeSeed: [],
+      tail: [],
+      solutions: [],
+      tests: [],
+      releasedOn: "",
+      type: "",
+      challengeType: 0,
+      nameCn: "",
+      descriptionCn: "",
+      nameFr: "",
+      descriptionFr: "",
+      nameRu: "",
+      descriptionRu: "",
+      nameEs: "",
+      descriptionEs: "",
+      namePt: "",
+      descriptionPt: ""
+    };
+
+    // Dimensions are given as [ Height, Width ]
+
+    this.editorLayout = {
+      meta: [
+        {name: "title", dimens: ['30px', 'auto']},
+        {name: "description", dimens: ['180px', 'auto']},
+        {name: "releasedOn", dimens: ['30px', 'auto']},
+        {name: "type", dimens: ['30px', 'auto']},
+        {name: "challengeType", dimens: ['30px', 'auto']}
+      ],
+      code: [
+        {name: "head", dimens: ['180px', 'auto']},
+        {name: "challengeSeed", dimens: ['360px', 'auto']},
+        {name: "tail", dimens: ['180px', 'auto']}
+      ],
+      test_solutions: [
+        {name: "solutions", dimens: ['240px', 'auto']},
+        {name: "tests", dimens: ['240px', 'auto']}
+      ],
+      localization: [
+        {name: "nameCn", dimens: ['30px', 'auto']},
+        {name: "descriptionCn", dimens: ['180px', 'auto']},
+        {name: "nameFr", dimens: ['30px', 'auto']},
+        {name: "descriptionFr", dimens: ['180px', 'auto']},
+        {name: "nameRu", dimens: ['30px', 'auto']},
+        {name: "descriptionRu", dimens: ['180px', 'auto']},
+        {name: "nameEs", dimens: ['30px', 'auto']},
+        {name: "descriptionEs", dimens: ['180px', 'auto']},
+        {name: "namePt", dimens: ['30px', 'auto']},
+        {name: "descriptionPt", dimens: ['180px', 'auto']}
+      ],
+      misc: [
+
+      ]
+    };
+
     this.state = {
       modalIsOpen: false
     };
@@ -152,24 +213,7 @@ class GrandCentralStation extends Component {
       newFileStoreObject = file;
 
       newFileStoreObject.challenges = newFileStoreObject.challenges.map((challenge) => {
-        return Object.assign({}, {
-          id: "",
-          title: "",
-          description: [],
-          head: [],
-          challengeSeed: [],
-          tail: [],
-          solutions: [],
-          tests: [],
-          releasedOn: "",
-          type: "",
-          challengeType: "",
-          nameCn: "",
-          nameFr: "",
-          nameRu: "",
-          nameEs: "",
-          namePt: ""
-        }, challenge);
+        return Object.assign({}, this.challengeSkeleton, challenge);
       });
 
       loadFile(dispatch, {
@@ -201,37 +245,10 @@ class GrandCentralStation extends Component {
       if (id === 'new') {
         $.getJSON('/mongoid', function(mongoid) {
           mongoid = mongoid.objectId;
-          oldFileStore.challenges.push({
+          oldFileStore.challenges.push(Object.assign({}, this.challengeSkeleton, {
             'id': mongoid,
-            'title': mongoid,
-            'description': [
-              ''
-            ],
-            'tests': [
-              ''
-            ],
-            'challengeSeed': [
-              ''
-            ],
-            'MDNlinks': [
-              ''
-            ],
-            'solutions': [
-              ''
-            ],
-            'type': '',
-            'challengeType': 0,
-            'nameCn': '',
-            'descriptionCn': [],
-            'nameFr': '',
-            'descriptionFr': [],
-            'nameRu': '',
-            'descriptionRu': [],
-            'nameEs': '',
-            'descriptionEs': [],
-            'namePt': '',
-            'descriptionPt': []
-          });
+            'title': mongoid
+          }));
 
           let AddedChallenge = {fileStore: oldFileStore};
 
@@ -381,7 +398,7 @@ class GrandCentralStation extends Component {
                 {leftNav}
                 {menu}
                 <div style = {{ 'marginTop': '70px' }}>
-                  <Editor id={this.props.activeChallenge.id} />
+                  <Editor editorLayout = {this.editorLayout} id={this.props.activeChallenge.id} />
                 </div>
               </div>
               {snackBar}
diff --git a/src/style.css b/src/style.css
index e69de29..2ec528c 100644
--- a/src/style.css
+++ b/src/style.css
@@ -0,0 +1,17 @@
+body {
+  font-family: 'Ubuntu';
+}
+
+.selectors {
+  display: flex;
+  width: 100%;
+}
+
+.selectors > .tabSelector {
+  flex: 1 0 auto;
+  background-color: rgb(51, 51, 51);
+  color: white;
+  text-align: center;
+  margin: 4px;
+  padding: 4px;
+}
\ No newline at end of file
diff --git a/views/layout.jade b/views/layout.jade
index 1df13fa..99576ef 100644
--- a/views/layout.jade
+++ b/views/layout.jade
@@ -2,6 +2,7 @@ doctype html
 html
   head
     title= title
+    link(href='https://fonts.googleapis.com/css?family=Ubuntu' rel='stylesheet' type='text/css')
   body
     #root
     script(src = "/dist/bundle.js")
\ No newline at end of file
