241
/* This will always return a listing, whether it is a dir or a file.
243
var listing = response.responseText;
244
/* The listing SHOULD be valid JSON text. Parse it into an object. */
247
listing = JSON.parse(listing);
248
file_listing = listing.listing; /* Global */
254
var err = document.createElement("div");
255
var p = dom_make_text_elem("p", "Error: "
256
+ "There was an unexpected server error processing "
257
+ "the selected command.");
259
p = dom_make_text_elem("p", "If the problem persists, please "
260
+ "contact the system administrator.")
262
p = document.createElement("p");
263
var refresh = document.createElement("input");
264
refresh.setAttribute("type", "button");
265
refresh.setAttribute("value", "Back to file view");
266
refresh.setAttribute("onclick", "refresh()");
267
p.appendChild(refresh);
273
var err = document.createElement("div");
274
var p = dom_make_text_elem("p", "Error: "
275
+ "There was an unexpected server error retrieving "
276
+ "the requested file or directory.");
278
p = dom_make_text_elem("p", "If the problem persists, please "
279
+ "contact the system administrator.")
285
/* Get "." out, it's special */
286
current_file = file_listing["."]; /* Global */
287
delete file_listing["."];
209
289
/* Check if this is a directory listing or file contents */
210
290
var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
211
if (!editmode && isdir)
213
var listing = response.responseText;
214
/* The listing SHOULD be valid JSON text. Parse it into an object. */
217
listing = JSON.parse(listing);
221
handle_error("The server returned an invalid directory listing");
224
293
handle_dir_listing(path, listing);
228
/* Treat this as an ordinary file. Get the file type. */
229
var content_type = response.getResponseHeader("Content-Type");
230
var handler_type = get_handler_type(content_type);
231
/* If we're in "edit mode", always treat this file as text */
232
would_be_handler_type = handler_type;
233
if (editmode) handler_type = "text";
234
/* handler_type should now be set to either
235
* "text", "image", "audio" or "binary". */
236
switch (handler_type)
241
handle_text(path_join(path, "untitled"), "",
242
would_be_handler_type);
246
handle_text(path, response.responseText,
247
would_be_handler_type);
251
/* TODO: Custom image handler */
252
handle_binary(path, response.responseText);
255
/* TODO: Custom audio handler */
256
handle_binary(path, response.responseText);
297
/* Need to make a 2nd ajax call, this time get the actual file
299
callback = function(response)
301
/* Read the response and set up the page accordingly */
302
handle_contents_response(path, response);
304
/* Call the server and request the listing. */
306
args = shallow_clone_object(url_args);
309
/* This time, get the contents of the file, not its metadata */
310
args['return'] = "contents";
311
ajax_call(callback, service_app, path, args, "GET");
313
update_actions(isdir);
316
function handle_contents_response(path, response)
318
/* Treat this as an ordinary file. Get the file type. */
319
var content_type = response.getResponseHeader("Content-Type");
320
var handler_type = get_handler_type(content_type);
321
would_be_handler_type = handler_type;
322
/* handler_type should now be set to either
323
* "text", "image", "audio" or "binary". */
324
switch (handler_type)
327
handle_text(path, response.responseText,
328
would_be_handler_type);
331
/* TODO: Custom image handler */
332
handle_binary(path, response.responseText);
335
/* TODO: Custom audio handler */
336
handle_binary(path, response.responseText);
344
/* Called when a form upload comes back (from an iframe).
345
* Refreshes the page.
347
function upload_callback()
349
/* This has a pretty nasty hack, which happens to work.
350
* upload_callback is set as the "onload" callback for the iframe which
351
* receives the response from the server for uploading a file.
352
* This means it gets called twice. Once when initialising the iframe, and
353
* a second time when the actual response comes back.
354
* All we want to do is call navigate to refresh the page. But we CAN'T do
355
* that on the first load or it will just go into an infinite cycle of
356
* refreshing. We need to refresh the page ONLY on the second refresh.
357
* upload_callback_count is reset to 0 just before the iframe is created.
359
upload_callback_count++;
360
if (upload_callback_count >= 2)
265
364
/** Deletes all "dynamic" content on the page.
285
383
dom_removechildren(document.getElementById("sidepanel"));
288
/** Sets the mode to either "file browser" or "text editor" mode.
289
* This modifies the window icon, and selected tab.
290
* \param editmode If True, editor mode. Else, file browser mode.
292
function setmode(editmode)
294
/* Find the DOM elements for the file browser and editor tabs */
295
var tabs = document.getElementById("apptabs");
296
var tab_files = null;
300
for (var i=0; i<tabs.childNodes.length; i++)
302
/* Find the href of the link within */
303
if (!tabs.childNodes[i].getElementsByTagName) continue;
304
a = tabs.childNodes[i].getElementsByTagName("a");
305
if (a.length == 0) continue;
306
href = a[0].getAttribute("href");
307
if (href == null) continue;
308
if (endswith(href, this_app))
309
tab_files = tabs.childNodes[i];
310
else if (endswith(href, edit_app))
311
tab_edit = tabs.childNodes[i];
316
tab_files.removeAttribute("class");
317
tab_edit.setAttribute("class", "thisapp");
321
tab_edit.removeAttribute("class");
322
tab_files.setAttribute("class", "thisapp");
326
386
/*** HANDLERS for different types of responses (such as dir listing, file,
390
* message may either be a string, or a DOM node, which will be placed inside
329
393
function handle_error(message)
332
395
var files = document.getElementById("filesbody");
333
var txt_elem = dom_make_text_elem("div", "Error: "
334
+ message.toString() + ".")
397
if (typeof(message) == "string")
399
txt_elem = dom_make_text_elem("div", "Error: "
400
+ message.toString() + ".")
404
/* Assume message is a DOM node */
405
txt_elem = document.createElement("div");
406
txt_elem.appendChild(message);
335
408
txt_elem.setAttribute("class", "padding error");
336
409
files.appendChild(txt_elem);
339
/** Presents a path list (address bar inside the page) for clicking.
341
function presentpath(path)
343
var dom_path = document.getElementById("path");
344
var href_path = make_path(this_app);
348
/* Also set the document title */
349
document.title = path_basename(path) + " - IVLE";
350
/* Create all of the paths */
351
var pathlist = path.split("/");
352
for (var i=0; i<pathlist.length; i++)
355
if (dir == "") continue;
356
/* Make an 'a' element */
357
href_path = path_join(href_path, dir);
358
nav_path = path_join(nav_path, dir);
359
var link = dom_make_link_elem("a", dir, "Navigate to " + nav_path,
360
href_path/*, "navigate(" + repr(href_path) + ")"*/);
361
dom_path.appendChild(link);
362
dom_path.appendChild(document.createTextNode("/"));
364
dom_path.removeChild(dom_path.lastChild);
367
412
/** Given a mime type, returns the path to the icon.
368
413
* \param type String, Mime type.
369
414
* \param sizelarge Boolean, optional.
428
472
div.appendChild(par2);
475
function update_actions()
478
var numsel = selected_files.length;
483
/* Display information about the current directory instead */
484
filename = path_basename(current_path);
487
else if (numsel == 1)
489
filename = selected_files[0];
490
file = file_listing[filename];
493
/* Update each action node in the topbar.
494
* This includes enabling/disabling actions as appropriate, and
495
* setting href/onclick attributes. */
499
/* Available if exactly one file is selected */
500
var open = document.getElementById("act_open");
503
open.setAttribute("class", "choice");
505
open.setAttribute("title",
506
"Navigate to this directory in the file browser");
508
open.setAttribute("title",
509
"Edit or view this file");
510
open.setAttribute("href", app_path(this_app, current_path, filename));
514
open.setAttribute("class", "disabled");
515
open.removeAttribute("title");
516
open.removeAttribute("href");
520
/* Available if zero or one files are selected,
521
* and only if this is a file, not a directory */
522
var serve = document.getElementById("act_serve");
523
if (numsel <= 1 && !file.isdir)
525
serve.setAttribute("class", "choice");
527
serve.setAttribute("href",
528
app_path(serve_app, current_path));
530
serve.setAttribute("href",
531
app_path(serve_app, current_path, filename));
535
serve.setAttribute("class", "disabled");
536
serve.removeAttribute("href");
540
/* Available if exactly one file is selected,
541
* and it is a Python file.
543
var run = document.getElementById("act_run");
545
if (numsel == 0 && !file.isdir && file.type == "text/x-python")
547
// In the edit window
548
run.setAttribute("class", "choice");
549
localpath = app_path('home',current_path);
550
run.setAttribute("onclick", "runfile('" + localpath + "')");
552
else if (numsel == 1 && !file.isdir && file.type == "text/x-python")
554
// In the browser window
555
run.setAttribute("class", "choice");
556
localpath = app_path('home',current_path,filename);
557
run.setAttribute("onclick", "runfile('" + localpath + "')");
561
run.setAttribute("class", "disabled");
562
run.removeAttribute("onclick");
567
* If 0 files selected, download the current file or directory as a ZIP.
568
* If 1 directory selected, download it as a ZIP.
569
* If 1 non-directory selected, download it.
570
* If >1 files selected, download them all as a ZIP.
572
var download = document.getElementById("act_download");
577
download.setAttribute("href",
578
app_path(download_app, current_path));
580
download.setAttribute("title",
581
"Download the current directory as a ZIP file");
583
download.setAttribute("title",
584
"Download the current file");
588
download.setAttribute("href",
589
app_path(download_app, current_path, filename));
591
download.setAttribute("title",
592
"Download the selected directory as a ZIP file");
594
download.setAttribute("title",
595
"Download the selected file");
600
/* Make a query string with all the files to download */
601
var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
602
for (var i=0; i<numsel; i++)
603
dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
604
dlpath = dlpath.substr(0, dlpath.length-1);
605
download.setAttribute("href", dlpath);
606
download.setAttribute("title",
607
"Download the selected files as a ZIP file");
610
/* Refresh - No changes required */
612
/* Publish and Submit */
613
/* If this directory is under subversion and selected/unselected file is a
615
var publish = document.getElementById("act_publish");
616
var submit = document.getElementById("act_submit");
617
if (numsel <= 1 && file.isdir)
619
/* TODO: Work out of file is svn'd */
620
publish.setAttribute("class", "choice");
621
publish.removeAttribute("disabled");
622
/* If this dir is already published, call it "Unpublish" */
625
publish.setAttribute("value", "unpublish");
626
publish.setAttribute("title" ,"Make it so this directory "
627
+ "can not be seen by anyone on the web");
628
publish.textContent = "Unpublish";
630
publish.setAttribute("value", "publish");
631
publish.setAttribute("title","Make it so this directory "
632
+ "can be seen by anyone on the web");
633
publish.textContent = "Publish";
635
submit.setAttribute("class", "choice");
636
submit.removeAttribute("disabled");
640
publish.setAttribute("class", "disabled");
641
publish.setAttribute("disabled", "disabled");
642
submit.setAttribute("class", "disabled");
643
submit.setAttribute("disabled", "disabled");
647
/* If exactly 1 non-directory file is selected/opened, and its parent
648
* directory is published.
650
var share = document.getElementById("act_share");
651
if (numsel <= 1 && !file.isdir)
653
/* Work out if parent dir is published */
654
parentdir = current_file;
655
if (parentdir.published)
657
share.setAttribute("class", "choice");
658
share.removeAttribute("disabled");
660
share.setAttribute("class", "disabled");
661
share.setAttribute("disabled", "disabled");
666
share.setAttribute("class", "disabled");
667
share.setAttribute("disabled", "disabled");
671
/* If exactly 1 file is selected */
672
var rename = document.getElementById("act_rename");
675
rename.setAttribute("class", "choice");
676
rename.removeAttribute("disabled");
680
rename.setAttribute("class", "disabled");
681
rename.setAttribute("disabled", "disabled");
684
/* Delete, cut, copy */
685
/* If >= 1 file is selected */
686
var act_delete = document.getElementById("act_delete");
687
var cut = document.getElementById("act_cut");
688
var copy = document.getElementById("act_copy");
691
act_delete.setAttribute("class", "choice");
692
act_delete.removeAttribute("disabled");
693
cut.setAttribute("class", "choice");
694
cut.removeAttribute("disabled");
695
copy.setAttribute("class", "choice");
696
copy.removeAttribute("disabled");
700
act_delete.setAttribute("class", "disabled");
701
act_delete.setAttribute("disabled", "disabled");
702
cut.setAttribute("class", "disabled");
703
cut.setAttribute("disabled", "disabled");
704
copy.setAttribute("class", "disabled");
705
copy.setAttribute("disabled", "disabled");
708
/* Paste, new file, new directory, upload */
709
/* Disable if the current file is not a directory */
710
if (!current_file.isdir)
712
var paste = document.getElementById("act_paste");
713
var newfile = document.getElementById("act_newfile");
714
var mkdir = document.getElementById("act_mkdir");
715
var upload = document.getElementById("act_upload");
716
paste.setAttribute("class", "disabled");
717
paste.setAttribute("disabled", "disabled");
718
newfile.setAttribute("class", "disabled");
719
newfile.setAttribute("disabled", "disabled");
720
mkdir.setAttribute("class", "disabled");
721
mkdir.setAttribute("disabled", "disabled");
722
upload.setAttribute("class", "disabled");
723
upload.setAttribute("disabled", "disabled");
726
/* Subversion actions */
727
var svnadd = document.getElementById("act_svnadd");
728
var svndiff = document.getElementById("act_svndiff");
729
var svnrevert = document.getElementById("act_svnrevert");
730
var svncommit = document.getElementById("act_svncommit");
731
/* These are only useful if we are in a versioned directory and have some
733
if (numsel >= 1 && current_file.svnstatus)
735
svnadd.setAttribute("class", "choice");
736
svnadd.removeAttribute("disabled");
737
svnrevert.setAttribute("class", "choice");
738
svnrevert.removeAttribute("disabled");
739
svncommit.setAttribute("class", "choice");
740
svncommit.removeAttribute("disabled");
744
svnadd.setAttribute("class", "disabled");
745
svnadd.setAttribute("disabled", "disabled");
746
svnrevert.setAttribute("class", "disabled");
747
svnrevert.setAttribute("disabled", "disabled");
748
svncommit.setAttribute("class", "disabled");
749
svncommit.setAttribute("disabled", "disabled");
752
/* Diff only supports one path at the moment. */
755
svnst = file_listing[selected_files[0]].svnstatus;
757
/* Diff also doesn't like unversioned paths, and diffs on unchanged
758
* files are pointless. */
759
if (svnst && svnst != "unversioned" && svnst != "normal")
761
svndiff.setAttribute("class", "choice");
762
svndiff.removeAttribute("disabled");
767
svndiff.setAttribute("class", "disabled");
768
svndiff.setAttribute("disabled", "disabled");
771
var svncheckout = document.getElementById("act_svncheckout");
772
/* current_path == username: We are at the top level */
773
if (current_path == username)
775
svncheckout.setAttribute("class", "choice");
776
svncheckout.removeAttribute("disabled");
780
svncheckout.setAttribute("class", "disabled");
781
svncheckout.setAttribute("disabled", "disabled");
784
/* There is currently nothing on the More Actions menu of use
785
* when the current file is not a directory. Hence, just remove
787
* (This makes some of the above decisions somewhat redundant).
789
if (!(current_file.isdir))
791
var moreactions = document.getElementById("moreactions_area");
792
moreactions.setAttribute("style", "display: none;");
798
/** Event handler for when an item of the "More actions..." dropdown box is
799
* selected. Performs the selected action. */
800
function handle_moreactions()
802
var moreactions = document.getElementById("moreactions");
803
if (moreactions.value == "top")
805
var selectedaction = moreactions.value;
806
/* Reset to "More actions..." */
807
moreactions.selectedIndex = 0;
809
/* If 0 files selected, filename is the name of the current dir.
810
* If 1 file selected, filename is that file.
812
if (selected_files.length == 0)
813
filename = path_basename(current_path);
814
else if (selected_files.length == 1)
815
filename = selected_files[0];
819
/* Now handle the selected action */
820
switch(selectedaction)
823
action_publish(selected_files);
826
action_unpublish(selected_files);
829
//alert("Not yet implemented: Sharing files");
830
window.open(public_app_path(serve_app, current_path, filename), 'share')
834
alert("Not yet implemented: Submit");
837
action_rename(filename);
840
action_remove(selected_files);
843
action_copy(selected_files);
846
action_cut(selected_files);
858
show_uploadpanel(true);
861
action_add(selected_files);
864
action_revert(selected_files);
867
window.location = path_join(app_path('diff'), current_path, selected_files[0]);
870
action_commit(selected_files);
878
/** User clicks "Run" button.
879
* Do an Ajax call and print the test output.
881
function runfile(localpath)
883
/* Dump the entire file to the console */
884
var callback = function()
886
console_enter_line("execfile('" + localpath + "')", "block");
888
start_server(callback)
431
892
/** Called when the page loads initially.
433
894
window.onload = function()