Merge lp:~isaacd-u/ensoft-sextant/program-selection into lp:ensoft-sextant
- program-selection
- Merge into whiteline
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 |
Related bugs: |
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}).
ChrisD (gingerchris) wrote : | # |
- 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.
ChrisD (gingerchris) : | # |
Preview Diff
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 | |
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 |
Looks good!
Added some inline comments for some bits that would be good to clean up.