Displaying differences for changeset
 
display as  

src/moplay/js/multiobj.js

@@ -235,7 +235,6 @@
     }
 };
 
-
 var csipOutput, csipGenCode;
 
 /**
@@ -522,6 +521,10 @@
                 "value": $('#problem_name').val()
             },
             {
+                "name": "public",
+                "value": document.getElementById('problem_public').checked
+            },
+            {
                 "name": "code",
                 "value": editor.getSession().getValue()
             },
@@ -557,15 +560,9 @@
     };
 }
 
-// run the code
-function runCode() {
+function startSpinner(element) {
     "use strict";
-    
-    console.log('Attempting run...');
 
-    var variables = getVariables('variables'),
-        request = getCSIPRequest();
-    
     // start spinner
     var opts = {
         lines: 13, // The number of lines to draw
@@ -585,9 +582,72 @@
         top: '50%', // Top position relative to parent
         left: '50%' // Left position relative to parent
     };
-    var target = document.getElementById('innerModal');
+    var target = document.getElementById(element);
     var spinner = new Spinner(opts).spin(target);
-    $(target).data('spinner', spinner);
+    return spinner;
+}
+
+// open the problems list dialog
+function openDialog() {
+    "use strict";
+
+    // open the window
+    window.location.href = '#openModal';
+
+    // start spinner
+    var spinner = startSpinner('openModalInner');
+
+    // get the data to send
+    var cursor_val = $('#cursor').val();
+    var data = {
+        limit: 20
+    };
+    if (cursor_val !== '') {
+        data.cursor = cursor_val;
+    }
+
+    // get the problems list
+    $.ajax({
+        url: '/problems_list',
+        type: 'GET',
+        dataType: 'json',
+        data: data,
+        success: function(response) {
+
+            // response.limit, response.cursor, response.more, response.problems
+            var problemsList = $('#problemsList');
+            problemsList.empty();
+
+            if (typeof response.problems !== 'undefined') {
+
+                if (response.problems.length == 0) {
+                    problemsList.append('<span>No optimization problems saved yet!</span>');
+                } else {
+                    response.problems.forEach( function(problem) {
+                        var button = document.createElement('a');
+                        button.className = "a-button-type padding-10";
+                        button.setAttribute('href', "/?id=" + problem.id);
+                        button.innerHTML = problem.name;
+                        problemsList.append(button);
+                    });
+                }
+
+            }
+
+            spinner.stop();
+        }
+    });
+}
+
+// run the code
+function runCode() {
+    "use strict";
+    
+    console.log('Attempting run...');
+
+    var variables = getVariables('variables'),
+        request = getCSIPRequest(),
+        spinner = startSpinner('chartModalInner');
 
     // print request
     console.log('request');
@@ -775,7 +835,7 @@
             };
 
             // stop the spinner
-            $(target).data('spinner').stop();
+            spinner.stop();
         }
     });
 }
@@ -847,3 +907,9 @@
     link.click();
 
 }
+
+// save code on Ctrl+s
+shortcut.add("Ctrl+S", function() {
+    saveCode();
+});
+

src/moplay/main.py

@@ -5,7 +5,7 @@
 from templates import env
 from runner import RunHandler
 from accounts import AccountHandler
-from problems import ProblemHandler, ProblemIDHandler, ProblemListHandler, ProblemTemplateHandler
+from problems import ProblemHandler, ProblemIDHandler, ProblemListHandler
 
 
 class MainHandler(webapp2.RequestHandler):
@@ -41,6 +41,5 @@
     ('/run', RunHandler),
     ('/problem', ProblemHandler),
     ('/problems_list', ProblemListHandler),
-    ('/problems_template', ProblemTemplateHandler),
-    ('/problems_id', ProblemIDHandler),
+    ('/problem_id', ProblemIDHandler),
 ], debug=True)

src/moplay/models.py

@@ -100,8 +100,8 @@
     variables = ndb.StructuredProperty(Variable, repeated=True)
 
     # dates
-    date_created = ndb.DateProperty(auto_now_add=True)
-    date_modified = ndb.DateProperty(auto_now=True)
+    date_created = ndb.DateTimeProperty(auto_now_add=True)
+    date_modified = ndb.DateTimeProperty(auto_now=True)
 
     def build_key(self):
         return Problem.build_key_from_name(self.name, self.owner_id)
@@ -167,9 +167,12 @@
 
         return json.dumps(self.csip_dict())
 
+    def exists(self):
+        return len(self.query(ancestor=self.key).fetch(1)) != 0
+
     @staticmethod
     def build_key_from_name(name, owner_id):
-        return ndb.Key(Problem, name, parent=Account.key_from_id(owner_id))
+        return ndb.Key('Problem', name, parent=Account.key_from_id(owner_id))
 
     @staticmethod
     def from_json(j_str, owner_id=None, member_ids=None):
@@ -180,11 +183,19 @@
     @staticmethod
     def from_dict(d, owner_id=None, member_ids=None):
 
-        # algorithm parameters
-        p = Problem()
+        # check existence of problem in datastore
         params = d["parameter"]
+        name = get_param(params, 'name')
+        if owner_id is not None and Problem.exists_by_name(name, owner_id):
+            p = Problem.retrieve_by_name(name, owner_id)
+        else:
+            p = Problem()
+            p.name = name
+            if owner_id is not None:
+                p.owner_id = owner_id
+                p.set_key()
+
         p.is_public = get_param(params, 'is_public', False)
-        p.name = get_param(params, 'name')
         p.method = get_param(params, 'method')
         p.max_evals = get_param(params, 'max_evals')
         p.population = get_param(params, 'population')
@@ -199,9 +210,6 @@
         # set
         if member_ids is not None:
             p.member_ids = member_ids
-        if owner_id is not None:
-            p.owner_id = owner_id
-            p.set_key()
 
         # return the session
         return p

src/moplay/problems.py

@@ -2,8 +2,8 @@
 import webapp2
 from templates import env
 from models import Problem
-from google.appengine.ext import ndb
 from google.appengine.api import users
+from google.appengine.datastore.datastore_query import Cursor
 
 
 class ProblemHandler(webapp2.RequestHandler):
@@ -73,14 +73,30 @@
 
     def get(self):
 
-        limit = self.request.get('limit', 10)
-        page = self.request.get('page', 1)
-        self.response.write({'limit': limit, 'page': page})
+        user = users.get_current_user()
+        if user:
 
+            limit = int(self.request.get('limit', 20))
+            cursor = self.request.get('cursor')
 
-class ProblemTemplateHandler(webapp2.RequestHandler):
+            q = Problem.query(Problem.owner_id == user.user_id()).order(-Problem.date_modified)
+            if cursor:
+                problems, next_cursor, more = q.fetch_page(limit, start_cursor=Cursor(urlsafe=cursor))
+            else:
+                problems, next_cursor, more = q.fetch_page(limit)
+            self.response.write(json.dumps({
+                'limit': limit,
+                'cursor': next_cursor.urlsafe(),
+                'more': more,
+                'problems': [{
+                    'name': p.name,
+                    'id': p.key.urlsafe(),
+                    'date_modified': str(p.date_modified)
+                } for p in problems]
+            }))
 
-    def get(self):
+        else:
 
-        problem_page = env.get_template('problems.html').render()
-        self.response.write(problem_page)
+            self.error(403)
+
+

src/moplay/stylesheets/main.css

@@ -109,7 +109,7 @@
 
 .problem-row {
     display: inherit;
-    margin: 0px auto;
+    margin: 0 auto;
     width: 100%;
     min-height: 36px;
 }
@@ -155,23 +155,53 @@
 .flex-container {
     display: -webkit-flex;
     display: flex;
-    flex-wrap: wrap;
     -webkit-flex-direction: row;
+    -moz-flex-direction: row;
+    -ms-flex-direction: row;
     flex-direction: row;
 }
 
-.flex-container-center {
+.flex-container-v {
     display: -webkit-flex;
+    display: -ms-flexbox;
     display: flex;
-    flex-wrap: wrap;
-    -webkit-flex-direction: row;
-    flex-direction: row;
+    -webkit-flex-direction: column;
+    -moz-flex-direction: column;
+    -ms-flex-direction: column;
+    flex-direction: column;
+    height: 100%;
+}
+
+.flex-center {
     -webkit-align-items: center;
+    -moz-align-items: center;
+    -ms-align-items: center;
     align-items: center;
     -webkit-justify-content: center;
+    -moz-justify-content: center;
+    -ms-justify-content: center;
     justify-content: center;
 }
 
+.flex-wrap {
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+
+.flex-fixed {
+    -webkit-flex: 0 0 auto;
+    -moz-flex: 0 0 auto;
+    -ms-flex: 0 0 auto;
+    flex: 0 0 auto;
+}
+
+.flex-fill-v {
+    -webkit-flex: 0 1 auto;
+    -moz-flex: 0 1 auto;
+    -ms-flex: 0 1 auto;
+    flex: 0 1 auto;
+}
+
 .param-row {
     display: inherit;
     -webkit-flex-direction: inherit;
@@ -233,14 +263,6 @@
     display: inherit;
 }
 
-.auto-margin {
-    margin: 0 auto;
-}
-
-.margin-top {
-    margin-top: 10px;
-}
-
 /*
  * BUTTON TYPES
  */
@@ -334,7 +356,47 @@
     background-size: 29px;
 }
 
+.link-button {
+    background: #000000 url("/images/link_white.png") no-repeat center;
+    background-size: 25px;
+}
+
 .open-button {
     background: #000000 url("/images/open_white.png") no-repeat center;
     background-size: 29px;
+}
+
+/*
+ * MISC
+ */
+.totally-hidden {
+    display: none;
+}
+
+.padding-10 {
+    padding: 10px 10px;
+}
+
+.padding-50 {
+    padding: 50px 50px;
+}
+
+.margin-10 {
+    margin: 10px 10px;
+}
+
+.margin-50 {
+    margin: 50px 50px;
+}
+
+.auto-margin {
+    margin: 0 auto;
+}
+
+.margin-top {
+    margin-top: 10px;
+}
+
+.fill-height {
+    max-height: 100%;
 }
\ No newline at end of file

src/moplay/stylesheets/modal.css

@@ -47,8 +47,57 @@
 /*
  * OPEN MODAL
  */
+.openModal {
 
+    font-weight: 900;
+    font-size: large;
 
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+
+    background: rgba(0,0,0,0.8);
+    z-index: 99999;
+
+    opacity:0;
+    -webkit-transition: opacity 400ms ease-in;
+    -moz-transition: opacity 400ms ease-in;
+    transition: opacity 400ms ease-in;
+    pointer-events: none;
+    color: whitesmoke;
+
+}
+
+.openModal:target {
+    opacity:1;
+    pointer-events: auto;
+}
+
+.openModal > div {
+    width: 80%;
+    height: calc(100% - 100px);
+    height: -webkit-calc(100% - 100px);
+    position: relative;
+    margin: 50px auto;
+    border-radius: 10px;
+
+    background: #606060;
+    /*background: -moz-linear-gradient(#606060, #646464);*/
+    /*background: -webkit-linear-gradient(#606060, #646464);*/
+    /*background: -o-linear-gradient(#606060, #646464);*/
+}
+
+.problems-list-container {
+    margin: 5px 5px;
+    overflow-y: auto;
+    height: 100%;
+}
+
+/*
+ * OTHER
+ */
 .close {
     background: #606061;
     color: #FFFFFF;

src/moplay/templates/index.html

@@ -17,6 +17,7 @@
     <script type="text/javascript" src="/js/jquery.dataTables.min.js"></script>
     <script type="text/javascript" src="/js/dataTables.tableTools.min.js"></script>
     <script type="text/javascript" src="/js/spin.min.js"></script>
+    <script type="text/javascript" src="/js/shortcut.js"></script>
     <script type="text/javascript" src="/js/multiobj.js"></script>
 
 </head>
@@ -34,6 +35,7 @@
         <div class="flex-container">
             <div class="param-row">
                 <div>
+                    <button onclick="openDialog();" class="a-button-type open-button" title="Load Optimization Problems"></button>
                     <button onclick="runCode();" class="button-type play-button" title="Run Optimization"></button>
                     <button id="modal-button" onclick="window.location.href = '#chartModal';" class="button-type chart-button hidden" title="Show Graph or Table"></button>
                     <button id="download-button" onclick="downloadOutput();" class="button-type download-button hidden" title="Download Results"></button>
@@ -41,11 +43,13 @@
                 </div>
                 <div class="flex-right">
                     {% if has_user %}
-                    <button onclick="openDialog();" class="button-type open-button" title="Open Different Optimization Problems"></button>
+                        {% if problem.key %}
+                        <button onclick="window.prompt('Press Ctrl+C to Copy the link below!', 'http://' + location.host + '/?id={{ problem.key.urlsafe() }}');" class="button-type link-button" title="Get Link to This Optimization Problem!"></button>
+                        {% endif %}
                     <button onclick="saveCode();" class="button-type save-button" title="Save this Optimization Problem"></button>
                     <a href="{{ user_url }}" class="a-button-type button-padding-extra">Sign Out</a>
                     {% else %}
-                    <a href="{{ user_url }}" class="a-button-type button-padding-extra" title="Sign in so you can save">Sign In To Save</a>
+                    <a href="{{ user_url }}" class="a-button-type button-padding-extra" title="Sign in so you can save your optimization problems!">Sign In With Google</a>
                     {% endif %}
                 </div>
             </div>
@@ -55,13 +59,17 @@
     <div id="parameters" class="left-panel">
         <div id="parameter_rows" class="flex-container">
 
-            <h2 class="param-row margin-top">Problem Name</h2>
+            <h2 class="param-row margin-top">Optimization Problem</h2>
 
             <div class="param-row">
-{#                <div id="problem_name" class="input button-padding-extra" contenteditable>{{ problem.name }}</div>#}
                 <input id="problem_name" type="text" class="margin-small param-full auto-margin" value="{{ problem.name }}"/>
             </div>
 
+            <div class="param-row">
+                <input id="problem_public" type="checkbox" class="" {{ "checked" if problem.public else "" }}/>
+                Make Public
+            </div>
+
             <h2 class="param-row">Algorithm Parameters</h2>
 
             <div class="param-row">
@@ -109,15 +117,27 @@
     <div id="editor">{{ problem.code.strip()|safe }}</div>
 
     <div id="chartModal" class="chartModal">
-        <div id="innerModal">
+        <div id="chartModalInner">
             <div id="container" class="contained-chart"></div>
             <a href="#close" title="Close" class="close">X</a>
         </div>
     </div>
 
     <div id="openModal" class="openModal">
-        <div>
+        <div id="openModalInner">
+            <div class="flex-container-v">
+                <div class="param-full flex-fixed">Header</div>
+                <div class="flex-fill-v problems-list-container">
+                    <div id="problemsList" class="flex-container flex-center flex-wrap">
 
+                    </div>
+                </div>
+                <div class="param-full flex-fixed">Footer</div>
+                <a href="#close" title="Close" class="close">X</a>
+            </div>
+        </div>
+        <div class="totally-hidden">
+            <div id="cursor"></div>
         </div>
     </div>