~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/media/browser/browser.js

ivle.webapp.testing: Add, with fake request and user.
ivle.webapp.base.test: Add! Test the JSONRESTView, using the new mocks.

Show diffs side-by-side

added added

removed removed

Lines of Context:
66
66
    "missing": "missing.png",
67
67
    "deleted": "deleted.png",
68
68
    "modified": "modified.png",
 
69
    "conflicted": "conflicted.png",
69
70
    "revision": "revision.png"
70
71
};
71
72
 
104
105
/** The listing object returned by the server as JSON */
105
106
file_listing = null;
106
107
current_file = null;
 
108
current_revision = null;
107
109
current_path = "";
108
110
 
109
111
/** Filenames of all files selected
143
145
             * to the user */
144
146
            var error = response.getResponseHeader("X-IVLE-Action-Error");
145
147
            if (error != null)
146
 
                alert("Error: " + error.toString() + ".");
 
148
                /* Note: This header (in particular) comes URI-encoded, to
 
149
                 * allow multi-line error messages. Decode */
 
150
                alert("Error: " + decodeURIComponent(error.toString()) + ".");
147
151
            /* Now read the response and set up the page accordingly */
148
152
            if (ignore_response != true)
149
 
                handle_response(path, response);
 
153
                handle_response(path, response, true);
150
154
        }
151
155
    /* Call the server and perform the action. This mutates the server. */
152
156
    ajax_call(callback, service_app, path, args, "POST", content_type);
165
169
    callback = function(response)
166
170
        {
167
171
            /* Read the response and set up the page accordingly */
168
 
            handle_response(path, response, url.args);
 
172
            handle_response(path, response, false, url.args);
169
173
        }
170
174
    /* Get any query strings */
171
175
    url = parse_url(window.location.href);
179
183
 */
180
184
function refresh()
181
185
{
182
 
    navigate(current_path);
 
186
    if (maybe_save('All changes since the last save will be lost!'))
 
187
        navigate(current_path);
183
188
}
184
189
 
185
190
/** Determines the "handler type" from a MIME type.
211
216
 * things) be used to update the URL in the location bar.
212
217
 * \param response XMLHttpRequest object returned by the server. Should
213
218
 * contain all the response data.
 
219
 * \param is_action Boolean. True if this is the response to an action, false
 
220
 * if this is the response to a simple listing. This is used in handling the
 
221
 * error.
214
222
 * \param url_args Arguments dict, for the arguments passed to the URL
215
223
 * in the browser's address bar (will be forwarded along).
216
224
 */
217
 
function handle_response(path, response, url_args)
 
225
function handle_response(path, response, is_action, url_args)
218
226
{
219
227
    /* TODO: Set location bar to "path" */
220
228
    current_path = path;
233
241
        return;
234
242
    }
235
243
 
 
244
    var subjects = null;
 
245
    var top_level_dir = path==username;
 
246
    if (top_level_dir)
 
247
    {
 
248
        var req = ajax_call(null, "userservice", "get_enrolments", null, "GET")
 
249
        subjects = decode_response(req);
 
250
    }
 
251
 
 
252
 
236
253
    /* This will always return a listing, whether it is a dir or a file.
237
254
     */
238
255
    var listing = response.responseText;
244
261
    }
245
262
    catch (e)
246
263
    {
247
 
        handle_error("The server returned an invalid directory listing");
 
264
        if (is_action)
 
265
        {
 
266
            var err = document.createElement("div");
 
267
            var p = dom_make_text_elem("p", "Error: "
 
268
                    + "There was an unexpected server error processing "
 
269
                    + "the selected command.");
 
270
            err.appendChild(p);
 
271
            p = dom_make_text_elem("p", "If the problem persists, please "
 
272
                    + "contact the system administrator.")
 
273
            err.appendChild(p);
 
274
            p = document.createElement("p");
 
275
            var refresh = document.createElement("input");
 
276
            refresh.setAttribute("type", "button");
 
277
            refresh.setAttribute("value", "Back to file view");
 
278
            refresh.setAttribute("onclick", "refresh()");
 
279
            p.appendChild(refresh);
 
280
            err.appendChild(p);
 
281
            handle_error(err);
 
282
        }
 
283
        else
 
284
        {
 
285
            var err = document.createElement("div");
 
286
            var p = dom_make_text_elem("p", "Error: "
 
287
                    + "There was an unexpected server error retrieving "
 
288
                    + "the requested file or directory.");
 
289
            err.appendChild(p);
 
290
            p = dom_make_text_elem("p", "If the problem persists, please "
 
291
                    + "contact the system administrator.")
 
292
            err.appendChild(p);
 
293
            handle_error(err);
 
294
        }
248
295
        return;
249
296
    }
250
297
    /* Get "." out, it's special */
251
298
    current_file = file_listing["."];     /* Global */
252
299
    delete file_listing["."];
253
300
 
 
301
    if ('revision' in listing)
 
302
    {
 
303
        current_revision = listing.revision;
 
304
    }
 
305
 
254
306
    /* Check if this is a directory listing or file contents */
255
307
    var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
256
308
    if (isdir)
257
309
    {
258
 
        handle_dir_listing(path, listing);
 
310
        setup_for_listing();
 
311
        home_listing(listing, subjects, path);
259
312
    }
260
313
    else
261
314
    {
323
376
     */
324
377
    upload_callback_count++;
325
378
    if (upload_callback_count >= 2)
 
379
    {
 
380
        myFrame = frames['upload_iframe'].document;
 
381
        data = myFrame.firstChild.childNodes[1].firstChild.firstChild.nodeValue;
 
382
        data = JSON.parse(data);
 
383
        if ('Error' in data)
 
384
            alert("Error: " + decodeURIComponent(data['Error']));
 
385
        document.getElementsByName('data')[0].value = '';
326
386
        refresh();
 
387
    }
327
388
}
328
389
 
329
390
/** Deletes all "dynamic" content on the page.
335
396
    dom_removechildren(document.getElementById("filesbody"));
336
397
}
337
398
 
 
399
/* Checks if a file needs to be saved. If it does, the user will be asked
 
400
 * if they want to continue anyway. The caller must specify a warning
 
401
 * sentence which indicates the consequences of continuing.
 
402
 * Returns true if we should continue, and false if we should not.
 
403
 */
 
404
function maybe_save(warning)
 
405
{
 
406
    if (warning == null) warning = '';
 
407
    if (current_file.isdir) return true;
 
408
    if (document.getElementById("save_button").disabled) return true;
 
409
    return confirm("This file has unsaved changes. " + warning +
 
410
                   "\nAre you sure you wish to continue?");
 
411
}
 
412
 
338
413
/** Deletes all "dynamic" content on the page necessary to navigate from
339
414
 * one directory listing to another (does not clear as much as clearpage
340
415
 * does).
351
426
/*** HANDLERS for different types of responses (such as dir listing, file,
352
427
 * etc). */
353
428
 
 
429
/* handle_error.
 
430
 * message may either be a string, or a DOM node, which will be placed inside
 
431
 * a div.
 
432
 */
354
433
function handle_error(message)
355
434
{
356
435
    var files = document.getElementById("filesbody");
357
 
    var txt_elem = dom_make_text_elem("div", "Error: "
358
 
        + message.toString() + ".")
 
436
    var txt_elem;
 
437
    if (typeof(message) == "string")
 
438
    {
 
439
        txt_elem = dom_make_text_elem("div", "Error: "
 
440
                   + message.toString() + ".")
 
441
    }
 
442
    else
 
443
    {
 
444
        /* Assume message is a DOM node */
 
445
        txt_elem = document.createElement("div");
 
446
        txt_elem.appendChild(message);
 
447
    }
359
448
    txt_elem.setAttribute("class", "padding error");
360
449
    files.appendChild(txt_elem);
361
450
}
362
451
 
 
452
/** Given a path, filename and optional revision, returns a URL to open that
 
453
 *  revision of that file.
 
454
 */
 
455
function build_revision_url(path, filename, revision)
 
456
{
 
457
    bits = {'path': app_path(this_app, path, filename)};
 
458
    if (current_revision)
 
459
    {
 
460
        bits['query_string'] = 'r=' + revision;
 
461
    }
 
462
    return build_url(bits);
 
463
}
 
464
 
363
465
/** Given a mime type, returns the path to the icon.
364
466
 * \param type String, Mime type.
365
467
 * \param sizelarge Boolean, optional.
423
525
    div.appendChild(par2);
424
526
}
425
527
 
 
528
/* Enable or disable actions1 moreactions actions. Takes either a single
 
529
 * name, or an array of them.*/
 
530
function set_action_state(names, which, allow_on_revision)
 
531
{
 
532
    if (!(names instanceof Array)) names = Array(names);
 
533
 
 
534
    for (var i=0; i < names.length; i++)
 
535
    {
 
536
        element = document.getElementById('act_' + names[i]);
 
537
        if (which &&
 
538
            !(current_file.svnstatus == 'revision' && !allow_on_revision))
 
539
        {
 
540
            /* Enabling */
 
541
            element.setAttribute("class", "choice");
 
542
            element.removeAttribute("disabled");
 
543
        }
 
544
        else
 
545
        {
 
546
            /* Disabling */
 
547
            element.setAttribute("class", "disabled");
 
548
            element.setAttribute("disabled", "disabled");
 
549
        }
 
550
    }
 
551
}
 
552
 
 
553
/* Updates the list of available actions based on files selected */
426
554
function update_actions()
427
555
{
428
556
    var file;
429
557
    var numsel = selected_files.length;
 
558
    var svn_selection = false;
 
559
    
 
560
    if (numsel > 0)
 
561
    {
 
562
        svn_selection = true;
 
563
        for (var i = 0; i < selected_files.length; i++){
 
564
            if (file_listing[selected_files[i]]["svnstatus"] == "unversioned")
 
565
            {
 
566
                svn_selection = false;        
 
567
            }
 
568
        }
 
569
    }
 
570
    
430
571
    if (numsel <= 1)
431
572
    {
432
573
        if (numsel == 0)
458
599
        else
459
600
            open.setAttribute("title",
460
601
                "Edit or view this file");
461
 
        open.setAttribute("href", app_path(this_app, current_path, filename));
 
602
        open.setAttribute("href", build_revision_url(current_path, filename,
 
603
                                                     current_revision));
462
604
    }
463
605
    else
464
606
    {
468
610
    }
469
611
 
470
612
    /* Serve */
471
 
    /* Available if exactly one file is selected,
 
613
    /* Available if zero or one files are selected,
472
614
     * and only if this is a file, not a directory */
473
615
    var serve = document.getElementById("act_serve");
474
 
    if (numsel == 1 && !file.isdir)
 
616
    if (numsel <= 1 && !file.isdir && current_file.svnstatus != 'revision')
475
617
    {
476
618
        serve.setAttribute("class", "choice");
477
 
        serve.setAttribute("href",
478
 
            app_path(serve_app, current_path, filename));
 
619
        serve.setAttribute("onclick",
 
620
              "return maybe_save('The last saved version will be served.')");
 
621
        if (numsel == 0)
 
622
            serve.setAttribute("href",
 
623
                app_path(serve_app, current_path));
 
624
        else
 
625
            serve.setAttribute("href",
 
626
                app_path(serve_app, current_path, filename));
479
627
    }
480
628
    else
481
629
    {
482
630
        serve.setAttribute("class", "disabled");
483
631
        serve.removeAttribute("href");
 
632
        serve.removeAttribute("onclick");
484
633
    }
485
634
 
486
635
    /* Run */
487
636
    /* Available if exactly one file is selected,
488
637
     * and it is a Python file.
489
638
     */
490
 
    /* TODO */
 
639
    var run = document.getElementById("act_run");
 
640
     
 
641
    if (numsel <= 1 && !file.isdir && file.type == "text/x-python" 
 
642
            && current_file.svnstatus != 'revision')
 
643
    {
 
644
        if (numsel == 0)
 
645
        {
 
646
            // In the edit window
 
647
            var localpath = path_join('/home', current_path);
 
648
        }
 
649
        else
 
650
        {
 
651
            // In the browser window
 
652
            var localpath = path_join('/home', current_path, filename);
 
653
        }
 
654
        run.setAttribute("class", "choice");
 
655
        run.setAttribute("onclick", "runfile('" + localpath + "')");
 
656
    }
 
657
    else
 
658
    {
 
659
        run.setAttribute("class", "disabled");
 
660
        run.removeAttribute("onclick");
 
661
    }
491
662
 
492
663
    /* Download */
493
 
    /* Always available.
 
664
    /* Always available for current files.
494
665
     * If 0 files selected, download the current file or directory as a ZIP.
495
666
     * If 1 directory selected, download it as a ZIP.
496
667
     * If 1 non-directory selected, download it.
497
668
     * If >1 files selected, download them all as a ZIP.
498
669
     */
499
670
    var download = document.getElementById("act_download");
500
 
    if (numsel <= 1)
501
 
    {
 
671
    if (current_file.svnstatus == 'revision')
 
672
    {
 
673
        download.setAttribute("class", "disabled");
 
674
        download.removeAttribute("onclick");
 
675
    }
 
676
    else if (numsel <= 1)
 
677
    {
 
678
        download.setAttribute("class", "choice")
502
679
        if (numsel == 0)
503
680
        {
504
681
            download.setAttribute("href",
529
706
        for (var i=0; i<numsel; i++)
530
707
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
531
708
        dlpath = dlpath.substr(0, dlpath.length-1);
 
709
        download.setAttribute("class", "choice")
532
710
        download.setAttribute("href", dlpath);
533
711
        download.setAttribute("title",
534
712
            "Download the selected files as a ZIP file");
541
719
     * directory. */
542
720
    var publish = document.getElementById("act_publish");
543
721
    var submit = document.getElementById("act_submit");
544
 
    if (numsel <= 1 && file.isdir)
545
 
    {
546
 
        /* TODO: Work out of file is svn'd */
547
 
        /* TODO: If this dir is already published, call it "Unpublish" */
548
 
        publish.setAttribute("class", "choice");
549
 
        publish.removeAttribute("disabled");
550
 
        submit.setAttribute("class", "choice");
551
 
        submit.removeAttribute("disabled");
552
 
    }
553
 
    else
554
 
    {
555
 
        publish.setAttribute("class", "disabled");
556
 
        publish.setAttribute("disabled", "disabled");
557
 
        submit.setAttribute("class", "disabled");
558
 
        submit.setAttribute("disabled", "disabled");
559
 
    }
 
722
    var pubcond = numsel <= 1 && file.isdir;
 
723
    if (pubcond)
 
724
    {
 
725
        /* If this dir is already published, call it "Unpublish" */
 
726
        if (file.published)
 
727
        {
 
728
            publish.setAttribute("value", "unpublish");
 
729
            publish.setAttribute("title" ,"Make it so this directory "
 
730
                + "can not be seen by anyone on the web");
 
731
            publish.textContent = "Unpublish";
 
732
        } else {
 
733
            publish.setAttribute("value", "publish");
 
734
            publish.setAttribute("title","Make it so this directory "
 
735
                + "can be seen by anyone on the web");
 
736
            publish.textContent = "Publish";
 
737
        }
 
738
    }
 
739
    set_action_state(["publish", "submit"], pubcond);
560
740
 
561
741
    /* Share */
562
 
    /* If exactly 1 non-directory file is selected/opened, and its parent
 
742
    /* If exactly 1 non-directory file is selected, and its parent
563
743
     * directory is published.
564
744
     */
565
 
    var share = document.getElementById("act_share");
566
 
    if (numsel <= 1 && !file.isdir)
567
 
    {
568
 
        /* TODO: Work out if parent dir is published */
569
 
        share.setAttribute("class", "choice");
570
 
        share.removeAttribute("disabled");
571
 
    }
572
 
    else
573
 
    {
574
 
        share.setAttribute("class", "disabled");
575
 
        share.setAttribute("disabled", "disabled");
576
 
    }
 
745
    set_action_state("share", numsel == 1 && !file.isdir &&
 
746
                     current_file.published);
577
747
 
578
748
    /* Rename */
579
749
    /* If exactly 1 file is selected */
580
 
    var rename = document.getElementById("act_rename");
581
 
    if (numsel == 1)
582
 
    {
583
 
        rename.setAttribute("class", "choice");
584
 
        rename.removeAttribute("disabled");
585
 
    }
586
 
    else
587
 
    {
588
 
        rename.setAttribute("class", "disabled");
589
 
        rename.setAttribute("disabled", "disabled");
590
 
    }
 
750
    set_action_state("rename", numsel == 1);
591
751
 
592
752
    /* Delete, cut, copy */
593
753
    /* If >= 1 file is selected */
594
 
    var act_delete = document.getElementById("act_delete");
595
 
    var cut = document.getElementById("act_cut");
596
 
    var copy = document.getElementById("act_copy");
597
 
    if (numsel >= 1)
598
 
    {
599
 
        act_delete.setAttribute("class", "choice");
600
 
        act_delete.removeAttribute("disabled");
601
 
        cut.setAttribute("class", "choice");
602
 
        cut.removeAttribute("disabled");
603
 
        copy.setAttribute("class", "choice");
604
 
        copy.removeAttribute("disabled");
605
 
    }
606
 
    else
607
 
    {
608
 
        act_delete.setAttribute("class", "disabled");
609
 
        act_delete.setAttribute("disabled", "disabled");
610
 
        cut.setAttribute("class", "disabled");
611
 
        cut.setAttribute("disabled", "disabled");
612
 
        copy.setAttribute("class", "disabled");
613
 
        copy.setAttribute("disabled", "disabled");
614
 
    }
 
754
    set_action_state(["delete", "cut", "copy"], numsel >= 1);
615
755
 
616
756
    /* Paste, new file, new directory, upload */
617
 
    /* Always enabled (assuming this is a directory) */
 
757
    /* Disable if the current file is not a directory */
 
758
    set_action_state(["paste", "newfile", "mkdir", "upload"], current_file.isdir);
618
759
 
619
760
    /* Subversion actions */
620
 
    /* TODO: Work out when these are appropriate */
621
 
    var svnadd = document.getElementById("act_svnadd");
622
 
    var svnrevert = document.getElementById("act_svnrevert");
623
 
    var svncommit = document.getElementById("act_svncommit");
624
 
    if (true)
625
 
    {
626
 
        svnadd.setAttribute("class", "choice");
627
 
        svnadd.removeAttribute("disabled");
628
 
        svnrevert.setAttribute("class", "choice");
629
 
        svnrevert.removeAttribute("disabled");
630
 
        svncommit.setAttribute("class", "choice");
631
 
        svncommit.removeAttribute("disabled");
632
 
    }
633
 
    var svncheckout = document.getElementById("act_svncheckout");
634
 
    /* current_path == username: We are at the top level */
635
 
    if (current_path == username)
636
 
    {
637
 
        svncheckout.setAttribute("class", "choice");
638
 
        svncheckout.removeAttribute("disabled");
 
761
    /* These are only useful if we are in a versioned directory and have some
 
762
     * files selected. */
 
763
    set_action_state(["svnadd",], numsel >= 1 && current_file.svnstatus);
 
764
    /* And these are only usefull is ALL the selected files are versioned */
 
765
    set_action_state(["svnremove", "svnrevert", "svncommit", "svncopy", 
 
766
            "svncut"], numsel >= 1 && current_file.svnstatus && svn_selection);
 
767
    
 
768
    /* Diff, log and update only support one path at the moment, so we must
 
769
     * have 0 or 1 versioned files selected. If 0, the directory must be
 
770
     * versioned. */
 
771
    single_versioned_path = (
 
772
         (
 
773
          (numsel == 1 && (svnst = file_listing[selected_files[0]].svnstatus)) ||
 
774
          (numsel == 0 && (svnst = current_file.svnstatus))
 
775
         ) && svnst != "unversioned");
 
776
    set_action_state(["svndiff", "svnupdate"], single_versioned_path);
 
777
 
 
778
    /* We can resolve if we have a file selected and it is conflicted. */
 
779
    set_action_state("svnresolved", single_versioned_path && numsel == 1 && svnst == "conflicted");
 
780
 
 
781
    /* Log should be available for revisions as well. */
 
782
    set_action_state("svnlog", single_versioned_path, true);
 
783
 
 
784
    /* There is currently nothing on the More Actions menu of use
 
785
     * when the current file is not a directory. Hence, just remove
 
786
     * it entirely.
 
787
     * (This makes some of the above decisions somewhat redundant).
 
788
     * We also take this opportunity to show the appropriate actions2
 
789
     * bar for this path. It should either be a save or upload widget.
 
790
     */
 
791
    if (current_file.isdir)
 
792
    {
 
793
        var actions2_directory = document.getElementById("actions2_directory");
 
794
        actions2_directory.setAttribute("style", "display: inline;");
 
795
        var moreactions = document.getElementById("moreactions_area");
 
796
        moreactions.setAttribute("style", "display: inline;");
639
797
    }
640
798
    else
641
799
    {
642
 
        svncheckout.setAttribute("class", "disabled");
643
 
        svncheckout.setAttribute("disabled", "disabled");
 
800
        var actions2_file = document.getElementById("actions2_file");
 
801
        actions2_file.setAttribute("style", "display: inline;");
644
802
    }
645
803
 
646
804
    return;
677
835
        action_unpublish(selected_files);
678
836
        break;
679
837
    case "share":
680
 
        // TODO
681
 
        alert("Not yet implemented: Sharing files");
 
838
        //alert("Not yet implemented: Sharing files");
 
839
        window.open(public_app_path(serve_app, current_path, filename), 'share')
682
840
        break;
683
841
    case "submit":
684
842
        // TODO
688
846
        action_rename(filename);
689
847
        break;
690
848
    case "delete":
691
 
        action_remove(selected_files);
 
849
        action_delete(selected_files);
692
850
        break;
693
851
    case "copy":
694
852
        action_copy(selected_files);
711
869
    case "svnadd":
712
870
        action_add(selected_files);
713
871
        break;
 
872
    case "svnremove":
 
873
        action_remove(selected_files);
 
874
        break;
714
875
    case "svnrevert":
715
876
        action_revert(selected_files);
716
877
        break;
 
878
    case "svndiff":
 
879
        window.location = path_join(app_path('diff'), current_path, selected_files[0] || '');
 
880
        break;
 
881
    case "svnupdate":
 
882
        action_update(selected_files);
 
883
        break;
 
884
    case "svnresolved":
 
885
        action_resolved(selected_files);
 
886
        break;
717
887
    case "svncommit":
718
888
        action_commit(selected_files);
719
889
        break;
720
 
    case "svncheckout":
721
 
        action_checkout();
722
 
        break;
723
 
    }
 
890
    case "svnlog":
 
891
        window.location = path_join(app_path('svnlog'), current_path, selected_files[0] || '');
 
892
        break;
 
893
    case "svncopy":
 
894
        action_svncopy(selected_files);
 
895
        break;
 
896
    case "svncut":
 
897
        action_svncut(selected_files);
 
898
        break;
 
899
    }
 
900
}
 
901
 
 
902
/** User clicks "Run" button.
 
903
 * Do an Ajax call and print the test output.
 
904
 */
 
905
function runfile(localpath)
 
906
{
 
907
    if (!maybe_save('The last saved version will be run.')) return false;
 
908
 
 
909
    /* Dump the entire file to the console */
 
910
    var callback = function()
 
911
    {
 
912
        console_enter_line("execfile('" + localpath + "')", "block");
 
913
    }
 
914
    start_server(callback)
 
915
    return;
724
916
}
725
917
 
726
918
/** Called when the page loads initially.
727
919
 */
728
 
window.onload = function()
 
920
function browser_init()
729
921
{
730
922
    /* Navigate (internally) to the path in the URL bar.
731
923
     * This causes the page to be populated with whatever is at that address,