Merge lp:~isaacd-u/ensoft-sextant/program-selection into lp:ensoft-sextant

Proposed by Isaac Dunn
Status: Merged
Approved by: ChrisD
Approved revision: 68
Merged at revision: 47
Proposed branch: lp:~isaacd-u/ensoft-sextant/program-selection
Merge into: lp:ensoft-sextant
Diff against target: 489 lines (+178/-52)
5 files modified
resources/sextant/web/index.html (+14/-11)
resources/sextant/web/interface.html (+14/-14)
resources/sextant/web/queryjavascript.js (+53/-16)
resources/sextant/web/style_sheet.css (+57/-7)
src/sextant/web/server.py (+40/-4)
To merge this branch: bzr merge lp:~isaacd-u/ensoft-sextant/program-selection
Reviewer Review Type Date Requested Status
ChrisD Approve
Review via email: mp+264265@code.launchpad.net

Commit message

Merging changes to interface (improved program selection).
Selection of the program to explore now happens on the front page, and a program can now be explored by accessing its specific URL (/program/{program name}), without going via the front page.

Description of the change

Changes to the user interface: program selection now happens on the front page, and a program can now be explored by accessing its specific URL (/program/{program name}).

To post a comment you must log in.
Revision history for this message
ChrisD (gingerchris) wrote :

Looks good!
Added some inline comments for some bits that would be good to clean up.

66. By Isaac Dunn <email address hidden>

Cache list of program names to prevent synchronously dealing with URL validation requests.
The list is initialised when the server is started, and is updated every time
the list of programs is requested (e.g. if index.html is accessed).

67. By Isaac Dunn <email address hidden>

Improved button colours (especially on hover).

68. By Isaac Dunn <email address hidden>

Merged Kath's changes.

Commits merged:
  Kath N 2015-07-09 Small changes to syntax of queryjavascript file, adding "use:strict" statement
  Kath N 2015-07-09 Tidied up queryjavascript.js, removed references to queries of type 'programs', since this is no longer used.

Revision history for this message
ChrisD (gingerchris) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'resources/sextant/web/index.html'
2--- resources/sextant/web/index.html 2014-09-04 09:46:18 +0000
3+++ resources/sextant/web/index.html 2015-07-09 15:37:33 +0000
4@@ -12,20 +12,23 @@
5 <link rel="stylesheet" type="text/css" href="style_sheet.css"/>
6 <title>Sextant</title>
7 </head>
8-<body style="height:100%" class="titlescreen">
9- <div class="centered">
10- <div id="titlescreen-logo-text">
11- Sextant
12- </div>
13- <div>
14- <button class="button" onclick="window.location='interface.html'">Open Sextant Explorer</button>
15- <!--<button class="button">View Programs</button>-->
16- </div>
17- </div>
18+<body style="height:100%" class="titlescreen" onload="get_names_for_autocomplete('programs_with_metadata')">
19+ <div id="wholebody">
20+ <div class="titlescreen-topbar">
21+ <div id="titlescreen-logo-text">Sextant</div>
22+ <p style="text-align:center"> Choose a program to explore: </p>
23+ </div>
24+
25+ <div id="main" class="main">
26+ <div id="programs_list" type="scrolling-block"> </div>
27+ </div>
28
29- <div id="titlescreen-bottombar">
30+ <div id="titlescreen-bottombar" type="titlescreen-bottombar">
31 To add a program to Sextant, use '<b>sextant add-program</b>' from the command line
32+ </div>
33+
34 </div>
35+ <script src="queryjavascript.js"></script>
36
37 </body>
38 </html>
39
40=== modified file 'resources/sextant/web/interface.html'
41--- resources/sextant/web/interface.html 2014-11-24 11:21:16 +0000
42+++ resources/sextant/web/interface.html 2015-07-09 15:37:33 +0000
43@@ -9,18 +9,16 @@
44 <html>
45 <head>
46 <meta charset="utf-8"/>
47- <link rel="stylesheet" type="text/css" href="style_sheet.css"/>
48+ <link rel="stylesheet" type="text/css" href="/style_sheet.css"/>
49 <title> Sextant Explorer </title>
50+ <script src="/queryjavascript.js"></script>
51 </head>
52
53-<body onload="get_names_for_autocomplete('programs'); set_arguments('', ''); show_item('welcome')">
54+<body onload="get_names_for_autocomplete('funcs_both'); set_arguments('', ''); show_item('welcome')">
55 <div class="toolbar">
56 <div>
57- Program:
58- <input list="program_names" id="program_name" class="textbox" style="width: 150px"
59- onchange="get_names_for_autocomplete('funcs_both')"/>
60- <datalist id="program_names"></datalist>
61-
62+ <button class="button" onclick="window.location.href='/'">Choose new program</button>
63+
64 <select id="query_list" class="dropdown" onchange="display_when();">
65 <option value="whole_program">Whole graph</option>
66 <option value="functions_calling">
67@@ -36,20 +34,20 @@
68 </select>
69 &nbsp;&nbsp;&nbsp;
70 <label>
71+ Suppress common functions?
72 <input type="checkbox" id="suppress_common" value="True"></input>
73- Suppress common functions?
74 </label>
75 <label>
76+ Limit to internal calls?
77 <input type="checkbox" id="limit_internal" value="True"></input>
78- Limit to internal calls?
79 </label>
80 <label>
81+ Maximum call depth:
82 <input type="number" id="max_depth" value="1" min="0" max="10"></input>
83- Maximum call depth.
84 </label>
85 <button class="button" style="float:right; margin: 1px 20px -1px 0;" onclick="execute_query()">Run Query</button>
86 </div>
87- <div id="toolbar-row2" style="margin-left: 234px;">
88+ <div id="toolbar-row2">
89 <!--list to populate arguments. Updates when program name is specified-->
90 <datalist id="function_names_1"></datalist>
91 <datalist id="function_names_2"></datalist>
92@@ -65,7 +63,7 @@
93
94
95 <!-- Output for image file-->
96- <img id=output_image src="sextant.jpg"
97+ <img id=output_image src="/sextant.gif"
98 class="pos_img"
99 style="align: bottom; font-style: italic; color: #C0C0C0; font-size: 15px" />
100
101@@ -74,7 +72,10 @@
102
103 <div class="centered" id="welcome">
104 <div class="title">Welcome to Sextant Explorer</div>
105- <div class="subtitle"><b>To begin</b>: enter a program name, choose a query, and click 'Run Query'</div>
106+ <div class="subtitle">
107+ Your current program is <b id="prog_name"></b><br>
108+ <b>To begin</b>: choose a query, and click 'Run Query'</div>
109+ <script>document.getElementById("prog_name").innerHTML = get_program_name()</script>
110 </div>
111 </div>
112
113@@ -90,6 +91,5 @@
114 </div>
115 </div>
116
117- <script src="queryjavascript.js"></script>
118 </body>
119 </html>
120
121=== modified file 'resources/sextant/web/queryjavascript.js'
122--- resources/sextant/web/queryjavascript.js 2014-12-02 14:00:57 +0000
123+++ resources/sextant/web/queryjavascript.js 2015-07-09 15:37:33 +0000
124@@ -5,27 +5,26 @@
125 //Runs query and "GET"s either program names uploaded on the
126 //server or function names from a specific program.
127
128-
129+"use strict";
130 var timeout = null;
131 var do_timeout = false;
132
133-
134 function get_names_for_autocomplete(info_needed, search) {
135 //Function queries to database to create a list
136- //which is used to populate the auto-complete text boxes.
137+ //which is used to populate the auto-complete text boxes,
138+ //and to add program buttons on index.html
139 if (typeof search == 'undefined') {
140 search = "";
141 }
142
143 var xmlhttp = new XMLHttpRequest();
144 xmlhttp.onreadystatechange = function() {
145- if (xmlhttp.status = 200) {
146+ if (xmlhttp.status == 200) {
147 var values_list = xmlhttp.responseText;
148 if (values_list != "") {
149 values_list = JSON.parse(values_list);
150- if (info_needed =='programs') {
151- //We need to populate the program names list
152- add_options("program_names", values_list);
153+ if (info_needed =='programs_with_metadata') {
154+ add_program_buttons_with_metadata(values_list);
155 do_timeout = false;
156 }
157 if (info_needed == 'funcs_with_timeout_1') {
158@@ -41,12 +40,11 @@
159 add_options("function_names_2", values_list);
160 do_timeout = false;
161 }
162-
163 }
164 }
165 }
166
167- if (info_needed == 'programs') {
168+ if (info_needed == 'programs_with_metadata') {
169 var string = "/database_properties?query=" + info_needed + "&program_name=";
170 xmlhttp.open("GET", string, true);
171 xmlhttp.send();
172@@ -61,7 +59,7 @@
173
174 var timeoutfn = function() {
175 var string = "/database_properties?query=" + "functions" +
176- "&program_name=" + document.getElementById("program_name").value +
177+ "&program_name=" + get_program_name() +
178 "&search=" + document.getElementById(target).value;
179 xmlhttp.open("GET", string, true);
180 xmlhttp.send();
181@@ -85,7 +83,7 @@
182 function add_options(selectedlist, values_list) {
183 //Adds all the options obtained from the list of program
184 //names or function names to an auto complete drop-down box
185- var options = ''
186+ var options = '';
187 if (values_list.length == 1 || values_list.length ==0) {
188 options += '<option value="'+values_list+'"/>';
189 } else {
190@@ -96,6 +94,35 @@
191 document.getElementById(selectedlist).innerHTML = options;
192 }
193
194+function add_program_buttons_with_metadata(values_list) {
195+ // Adds one button for each available program to the button list (in index.html)
196+ // Also adds the required metadata, currently:
197+ // {program_name} (in bold)
198+ // User: {uploader}
199+ // Date: {date}
200+
201+ // sorts the programs so that most recently uploaded appear first
202+ var field = "date";
203+ values_list.sort(function(x,y) {
204+ if (x[field] > y[field]) {
205+ return -1;
206+ }
207+ if (x[field] < y[field]) {
208+ return 1;
209+ }
210+ return 0;
211+ });
212+
213+ var buttons = '';
214+ for (var i=0; i < values_list.length;++i) {
215+ // Add button with id = program name and onclick = go to /program/{program name} and text as required
216+ var date = values_list[i]["date"].split(' ')[0];
217+ buttons += '<button type="button" class="program_button" id=' + values_list[i]["program_name"] +
218+ ' onclick=window.location="/program/' + values_list[i]["program_name"] + '">' + '<b>' + values_list[i]["program_name"] + '</b> <br> <small>User: </small>' + values_list[i]["uploader"] + '<br> <small>Date: </small>' + date + '</button>';
219+ }
220+ document.getElementById("programs_list").innerHTML = buttons;
221+}
222+
223
224 function set_argument(number, label) {
225 var argEl = document.getElementById("argument_" + number);
226@@ -126,7 +153,7 @@
227 var query_list = document.getElementById("query_list");
228
229 var no_functions = new Array();
230- var prog_name = document.getElementById("program_name").value;
231+ var prog_name = get_program_name();
232 if (query_list.options[query_list.selectedIndex].value == "functions_calling") {
233 set_arguments("Function being called", "");
234 }
235@@ -151,7 +178,7 @@
236
237 function execute_query() {
238 document.getElementById("output_image").src = "";
239-
240+
241 //Returns error in alert window if query not executed properly,
242 //otherwise performs the query and outputs it
243 show_item("please-wait");
244@@ -159,14 +186,14 @@
245 if (query_id == "function_names") {
246 //url for page containing all function names
247 var string = "/database_properties?program_name=" +
248- document.getElementById("program_name").value + "&query=functions";
249+ get_program_name() + "&query=functions";
250 } else {
251 //If not function names we will want a graph as an output;
252 //url returns svg file of graph.
253 // We use a random number argument to prevent caching.
254 var string = "/output_graph.svg?stop_cache=" + String(Math.random()) + "&program_name=" +
255- document.getElementById("program_name").value +
256- "&query=" + query_id + "&function_calling=";
257+ get_program_name() +
258+ "&query=" + query_id + "&function_calling=";
259 string = string + document.getElementById("function_1").value +
260 "&function_called=" + document.getElementById("function_2").value;
261 string = string + "&suppress_common=" +
262@@ -232,3 +259,13 @@
263 show_item("error");
264 document.getElementById("error-msg").innerHTML = msg;
265 }
266+
267+function get_program_name() {
268+ // Provided there is a program name in the url (expected form is localhost:#/program/prog_name
269+ // this returns the prog_name
270+
271+ var url = document.URL;
272+ var prog_name = url.split('/').reverse()[0];
273+ return prog_name;
274+}
275+
276
277=== modified file 'resources/sextant/web/style_sheet.css'
278--- resources/sextant/web/style_sheet.css 2014-09-04 09:46:18 +0000
279+++ resources/sextant/web/style_sheet.css 2015-07-09 15:37:33 +0000
280@@ -15,8 +15,9 @@
281 padding: 0;
282 }
283
284+
285 body.titlescreen {
286- background-color: rgb(105, 145, 172);
287+ background: rgb(105, 145, 172);
288 color: rgb(245, 245, 245);
289 }
290
291@@ -40,37 +41,56 @@
292 text-align:center; background:#000000
293 }
294
295-
296-
297-
298+div.main {
299+ height:auto;
300+ padding:5px;
301+ padding-top:10px;
302+ margin-bottom:60px;
303+ width:60%;
304+ margin-left:20%;
305+ text-align:center;
306+}
307
308 div.centered {
309 position: absolute;
310 left: 50%;
311 top: 40%; /* for good design we position nearer the top */
312- transform: translate(-50%, -40%);
313+ transform: translate(-50%, -40%);
314 -webkit-transform: translate(-50%, -40%);
315 -moz-transform: translate(-50%, -40%);
316 -ms-transform: translate(-50%, -40%);
317 text-align: center;
318 }
319
320+scrolling-block {
321+ height:auto;
322+ overflow-y:auto;
323+}
324+
325 #titlescreen-logo-text {
326 font-size: 80px;
327 font-family: 'Poiret One', sans-serif;
328 letter-spacing: -2px;
329 padding-bottom: 4px;
330+ text-align:center;
331 }
332
333 #titlescreen-bottombar {
334 font-size: 20px;
335- position: absolute;
336+ position: fixed;
337 left: 0;
338 bottom: 0;
339 right: 0;
340- height: 80px;
341+ height: 40px;
342 font-weight: 300;
343 text-align: center;
344+ background:rgb(105,145,172);
345+}
346+
347+#titlescreen-topbar {
348+ position:fixed;
349+ height:60px;
350+ background:rgb(105,145,172);
351 }
352
353 .toolbar {
354@@ -90,6 +110,36 @@
355 border: none;
356 }
357
358+.program_button {
359+ background: rgb(135, 175, 202);
360+ background: -moz-linear-gradient(top,rgb(135, 175, 202) 0%,rgb(95, 135, 162) 100%);
361+ background: -webkit-gradient(linear,left top,left bottom,color-stop(0%,rgb(135, 175, 202)),color-stop(100%,rgb(95, 135, 162)));
362+ background: -webkit-linear-gradient(top,rgb(135, 175, 202) 0%,rgb(95, 135, 162) 100%);
363+ background: -o-linear-gradient(top,rgb(135, 175, 202) 0%,rgb(95, 135, 162) 100%);
364+ background: -ms-linear-gradient(top,rgb(135, 175, 202) 0%,rgb(95, 135, 162) 100%);
365+ background: linear-gradient(top,rgb(135, 175, 202) 0%,rgb(95, 135, 162) 100%);
366+ filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='rgb(135, 175, 202)',endColorstr='rgb(95, 135, 162)',GradientType=0);
367+ padding:8px 13px;
368+ color:#fff;
369+ font-family:'Source Sans Pro',sans-serif;
370+ font-size:17px;
371+ border-radius:4px;
372+ -moz-border-radius:4px;
373+ -webkit-border-radius:4px;
374+ border:1px solid rgb(95, 135, 162)
375+}
376+
377+.program_button:hover{
378+ background: rgb(125, 165, 192);
379+ background: -moz-linear-gradient(top,rgb(155, 195, 222) 0%,rgb(125, 165, 192) 100%);
380+ background: -webkit-gradient(linear,left top,left bottom,color-stop(0%,rgb(155, 195, 222)),color-stop(100%,rgb(125, 165, 192)));
381+ background: -webkit-linear-gradient(top,rgb(155, 195, 222) 0%,rgb(125, 165, 192) 100%);
382+ background: -o-linear-gradient(top,rgb(155, 195, 222) 0%,rgb(125, 165, 192) 100%);
383+ background: -ms-linear-gradient(top,rgb(155, 195, 222) 0%,rgb(125, 165, 192) 100%);
384+ background: linear-gradient(top,rgb(155, 195, 222) 0%,rgb(125, 165, 192) 100%);
385+ filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='rgb(155, 195, 222)',endColorstr='rgb(125, 165, 192)',GradientType=0);
386+}
387+
388 .textbox {
389 background-color: rgb(245, 245, 245);
390 border: 1px solid rgb(103, 114, 122);
391
392=== modified file 'src/sextant/web/server.py'
393--- src/sextant/web/server.py 2014-12-18 17:00:51 +0000
394+++ src/sextant/web/server.py 2015-07-09 15:37:33 +0000
395@@ -7,7 +7,7 @@
396 # Note: this must be run in Python 2.
397
398 from twisted.web.server import Site, NOT_DONE_YET
399-from twisted.web.resource import Resource
400+from twisted.web.resource import Resource, NoResource
401 from twisted.web.static import File
402 from twisted.internet import reactor
403 from twisted.internet.threads import deferToThread
404@@ -33,6 +33,10 @@
405 # global SextantConnection object which deals with the port forwarding
406 CONNECTION = None
407
408+# global list of program names for validation - prevents synchronously fetching
409+# on page loads, occupying the server
410+PROGRAM_NAMES = []
411+
412 # The maximum number of matches to display for autocomplete. If more than
413 # this are matched, nothing will be returned.
414 AUTOCOMPLETE_NAMES_LIMIT = 75
415@@ -250,7 +254,19 @@
416
417 @staticmethod
418 def _get_program_names():
419- return CONNECTION.get_program_names()
420+ # Keep list used for URL validation up to date
421+ global PROGRAM_NAMES
422+ PROGRAM_NAMES = CONNECTION.get_program_names()
423+ return PROGRAM_NAMES
424+
425+ @staticmethod
426+ def _get_programs_with_metadata():
427+ # Converts set of namedtuples to a list of dictionaries
428+ # for serialisation into JSON.
429+ global PROGRAM_NAMES
430+ progs_with_metadata = CONNECTION.programs_with_metadata()
431+ PROGRAM_NAMES = [ntuple.program_name for ntuple in progs_with_metadata]
432+ return [ntuple._asdict() for ntuple in progs_with_metadata]
433
434 @staticmethod
435 def _get_function_names(program_name, search=""):
436@@ -283,6 +299,13 @@
437 request.finish()
438 defer.returnValue(None)
439
440+ elif query == 'programs_with_metadata':
441+ request.setHeader("content-type", "application/json")
442+ prog_metadata = yield deferToThread(self._get_programs_with_metadata)
443+ request.write(json.dumps(prog_metadata))
444+ request.finish()
445+ defer.returnValue(None)
446+
447 elif query == 'functions':
448 if "program_name" not in request.args:
449 request.setResponseCode(400)
450@@ -310,7 +333,7 @@
451 else:
452 request.setResponseCode(400)
453 request.setHeader("content-type", "text/plain")
454- request.write("'Query' parameter should be 'programs' or 'functions'.")
455+ request.write("'Query' parameter should be 'programs', 'programs_with_metadata' or 'functions'.")
456 request.finish()
457 defer.returnValue(None)
458
459@@ -320,10 +343,12 @@
460
461
462 def serve_site(connection, port):
463- global CONNECTION
464+ global CONNECTION, PROGRAM_NAMES
465
466 CONNECTION = connection
467
468+ # saves making this synchronous call every page load
469+ PROGRAM_NAMES = CONNECTION.get_program_names()
470
471 # serve static directory at root
472 root = File(os.path.join(environment.RESOURCES_DIR, 'sextant', 'web'))
473@@ -331,6 +356,17 @@
474 # serve a dynamic Echoer webpage at /echoer.html
475 root.putChild("echoer.html", Echoer())
476
477+ # serve static files at /program/{anything}
478+ grandchild = File(os.path.join(environment.RESOURCES_DIR, 'sextant', 'web', 'interface.html'))
479+ class ServeInterface(Resource): # all children of this class are grandchild
480+ def getChild(self, name, request):
481+ if not name in PROGRAM_NAMES:
482+ return NoResource("The program {} was not found in the database.".format(name)
483+ + "<br>If you just added that program, please go to the front page first.")
484+ else:
485+ return grandchild
486+ root.putChild("program", ServeInterface())
487+
488 # serve a dynamic webpage at /Sextant_properties to return graph properties
489 root.putChild("database_properties", GraphProperties())
490

Subscribers

People subscribed via source and target branches