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

« back to all changes in this revision

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

  • Committer: mattgiuca
  • Date: 2007-12-12 01:38:59 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:37
Added src/dispatch/request.py: The IVLE Request class.
dispatch module now transforms the Apache request object into an IVLE one, and
goes through it. (Note this temporarily means the content type is no longer
set, because the request object is missing functionality).

Added brief description of this object in notes/apps/dispatch.txt.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* IVLE - Informatics Virtual Learning Environment
2
 
 * Copyright (C) 2007-2008 The University of Melbourne
3
 
 *
4
 
 * This program is free software; you can redistribute it and/or modify
5
 
 * it under the terms of the GNU General Public License as published by
6
 
 * the Free Software Foundation; either version 2 of the License, or
7
 
 * (at your option) any later version.
8
 
 *
9
 
 * This program is distributed in the hope that it will be useful,
10
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
 * GNU General Public License for more details.
13
 
 *
14
 
 * You should have received a copy of the GNU General Public License
15
 
 * along with this program; if not, write to the Free Software
16
 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
 
 *
18
 
 * Module: File Browser (client)
19
 
 * Author: Matt Giuca
20
 
 * Date: 11/1/2008
21
 
 */
22
 
 
23
 
/* Url names for apps */
24
 
this_app = "files";
25
 
edit_app = "edit";
26
 
service_app = "fileservice";
27
 
serve_app = "serve";
28
 
download_app = "download";
29
 
 
30
 
/* Mapping MIME types onto handlers.
31
 
 * "text" : When navigating to a text file, the text editor is opened.
32
 
 * "image" : When navigating to an image, the image is displayed (rather than
33
 
 *              going to the text editor).
34
 
 * "audio" : When navigating to an audio file, a "play" button is presented.
35
 
 * "binary" : When navigating to a binary file, offer it as a download through
36
 
 *              "serve".
37
 
 *
38
 
 * If a file is not on the list, its default action is determined by the first
39
 
 * part of its content type, where "text/*", "image/*" and "audio/*" are
40
 
 * treated as above, and other types are simply treated as binary.
41
 
 */
42
 
type_handlers = {
43
 
    "application/x-javascript" : "text",
44
 
    "application/javascript" : "text",
45
 
    "application/json" : "text",
46
 
    "application/xml" : "text"
47
 
};
48
 
 
49
 
/* Mapping MIME types to icons, just the file's basename */
50
 
type_icons = {
51
 
    "text/directory": "dir.png",
52
 
    "text/x-python": "py.png"
53
 
};
54
 
 
55
 
default_type_icon = "txt.png";
56
 
 
57
 
/* Relative to IVLE root */
58
 
type_icons_path = "media/images/mime";
59
 
type_icons_path_large = "media/images/mime/large";
60
 
 
61
 
/* Mapping SVN status to icons, just the file's basename */
62
 
svn_icons = {
63
 
    "unversioned": null,
64
 
    "normal": "normal.png",
65
 
    "added": "added.png",
66
 
    "missing": "missing.png",
67
 
    "deleted": "deleted.png",
68
 
    "modified": "modified.png",
69
 
    "conflicted": "conflicted.png",
70
 
    "revision": "revision.png"
71
 
};
72
 
 
73
 
/* Mapping SVN status to "nice" strings */
74
 
svn_nice = {
75
 
    "unversioned": "Temporary file",
76
 
    "normal": "Permanent file",
77
 
    "added": "Temporary file (scheduled to be added)",
78
 
    "missing": "Permanent file (missing)",
79
 
    "deleted": "Permanent file (scheduled for deletion)",
80
 
    "replaced": "Permanent file (replaced)",
81
 
    "modified": "Permanent file (modified)",
82
 
    "merged": "Permanent file (merged)",
83
 
    "conflicted": "Permanent file (conflicted)",
84
 
    "revision": "Past Permanent file (revision)"
85
 
};
86
 
 
87
 
default_svn_icon = null;
88
 
default_svn_nice = "Unknown status";
89
 
 
90
 
svn_icons_path = "media/images/svn";
91
 
 
92
 
published_icon = "media/images/interface/published.png";
93
 
 
94
 
/* List of MIME types considered "executable" by the system.
95
 
 * Executable files offer a "run" link, implying that the "serve"
96
 
 * application can interpret them.
97
 
 */
98
 
types_exec = [
99
 
    "text/x-python"
100
 
];
101
 
 
102
 
 
103
 
/* Global variables */
104
 
 
105
 
/** The listing object returned by the server as JSON */
106
 
file_listing = null;
107
 
current_file = null;
108
 
current_revision = null;
109
 
current_path = "";
110
 
 
111
 
/** Filenames of all files selected
112
 
 * (Only used by dir listings, but still needs to be [] for files, so that
113
 
 * update_actions knows that nothing is selected).
114
 
 */
115
 
selected_files = [];
116
 
 
117
 
upload_callback_count = 0;      /* See upload_callback */
118
 
 
119
 
/** Calls the server using Ajax, performing an action on the server side.
120
 
 * Receives the response from the server and performs a refresh of the page
121
 
 * contents, updating it to display the returned data (such as a directory
122
 
 * listing, file preview, or editor pane).
123
 
 * Always makes a POST request.
124
 
 * No return value.
125
 
 *
126
 
 * \param action String. Name of the action to perform, as defined in the
127
 
 *     fileservice API.
128
 
 * \param path URL path to make the request to, within the application.
129
 
 * \param args Argument object, as described in util.parse_url and friends.
130
 
 *      This should contain the arguments to the action, but NOT the action
131
 
 *      itself. (Also a minor side-effect; the "args" object will be mutated
132
 
 *      to include the action attribute).
133
 
 * \param content_type String, optional.
134
 
 *      May be "application/x-www-form-urlencoded" or "multipart/form-data".
135
 
 *      Defaults to "application/x-www-form-urlencoded".
136
 
 *      "multipart/form-data" is recommended for large uploads.
137
 
 */
138
 
function do_action(action, path, args, content_type, ignore_response)
139
 
{
140
 
    args.action = action;
141
 
    /* Callback action, when the server returns */
142
 
    var callback = function(response)
143
 
        {
144
 
            /* Check for action errors reported by the server, and report them
145
 
             * to the user */
146
 
            var error = response.getResponseHeader("X-IVLE-Action-Error");
147
 
            if (error != null)
148
 
                /* Note: This header (in particular) comes URI-encoded, to
149
 
                 * allow multi-line error messages. Decode */
150
 
                alert("Error: " + decodeURIComponent(error.toString()) + ".");
151
 
            /* Now read the response and set up the page accordingly */
152
 
            if (ignore_response != true)
153
 
                handle_response(path, response, true);
154
 
        }
155
 
    /* Call the server and perform the action. This mutates the server. */
156
 
    ajax_call(callback, service_app, path, args, "POST", content_type);
157
 
}
158
 
 
159
 
/** Calls the server using Ajax, requesting a directory listing. This should
160
 
 * not modify the server in any way. Receives the response from the server and
161
 
 * performs a refresh of the page contents, updating it to display the
162
 
 * returned data (such as a directory listing, file preview, or editor pane).
163
 
 * Called "navigate", can also be used for a simple refresh.
164
 
 * Always makes a GET request.
165
 
 * No return value.
166
 
 */
167
 
function navigate(path)
168
 
{
169
 
    callback = function(response)
170
 
        {
171
 
            /* Read the response and set up the page accordingly */
172
 
            handle_response(path, response, false, url.args);
173
 
        }
174
 
    /* Get any query strings */
175
 
    url = parse_url(window.location.href);
176
 
    
177
 
    /* Call the server and request the listing. */
178
 
    ajax_call(callback, service_app, path, url.args, "GET");
179
 
}
180
 
 
181
 
/* Refreshes the current view.
182
 
 * Calls navigate on the current path.
183
 
 */
184
 
function refresh()
185
 
{
186
 
    if (maybe_save('All changes since the last save will be lost!'))
187
 
        navigate(current_path);
188
 
}
189
 
 
190
 
/** Determines the "handler type" from a MIME type.
191
 
 * The handler type is a string, either "text", "image", "audio" or "binary".
192
 
 */
193
 
function get_handler_type(content_type)
194
 
{
195
 
    if (!content_type)
196
 
        return null;
197
 
    if (content_type in type_handlers)
198
 
        return type_handlers[content_type];
199
 
    else
200
 
    {   /* Based on the first part of the MIME type */
201
 
        var handler_type = content_type.split('/')[0];
202
 
        if (handler_type != "text" && handler_type != "image" &&
203
 
            handler_type != "audio")
204
 
            handler_type = "binary";
205
 
        return handler_type;
206
 
    }
207
 
}
208
 
 
209
 
/** Given an HTTP response object, cleans up and rebuilds the contents of the
210
 
 * page using the response data. This does not navigate away from the page, it
211
 
 * merely rebuilds most of the data.
212
 
 * Note that depending on the type of data returned, this could result in a
213
 
 * directory listing, an image preview, an editor pane, etc.
214
 
 * Figures out the type and calls the appropriate function.
215
 
 * \param path URL path which the request was made for. This can (among other
216
 
 * things) be used to update the URL in the location bar.
217
 
 * \param response XMLHttpRequest object returned by the server. Should
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.
222
 
 * \param url_args Arguments dict, for the arguments passed to the URL
223
 
 * in the browser's address bar (will be forwarded along).
224
 
 */
225
 
function handle_response(path, response, is_action, url_args)
226
 
{
227
 
    /* TODO: Set location bar to "path" */
228
 
    current_path = path;
229
 
 
230
 
    /* Clear away the existing page contents */
231
 
    clearpage();
232
 
 
233
 
    /* Check the status, and if not 200, read the error and handle this as an
234
 
     * error. */
235
 
    if (response.status != 200)
236
 
    {
237
 
        var error = response.getResponseHeader("X-IVLE-Return-Error");
238
 
        if (error == null)
239
 
            error = response.statusText;
240
 
        handle_error(error);
241
 
        return;
242
 
    }
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
 
 
253
 
    /* This will always return a listing, whether it is a dir or a file.
254
 
     */
255
 
    var listing = response.responseText;
256
 
    /* The listing SHOULD be valid JSON text. Parse it into an object. */
257
 
    try
258
 
    {
259
 
        listing = JSON.parse(listing);
260
 
        file_listing = listing.listing;     /* Global */
261
 
    }
262
 
    catch (e)
263
 
    {
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
 
        }
295
 
        return;
296
 
    }
297
 
    /* Get "." out, it's special */
298
 
    current_file = file_listing["."];     /* Global */
299
 
    delete file_listing["."];
300
 
 
301
 
    if ('revision' in listing)
302
 
    {
303
 
        current_revision = listing.revision;
304
 
    }
305
 
 
306
 
    /* Check if this is a directory listing or file contents */
307
 
    var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
308
 
    if (isdir)
309
 
    {
310
 
        setup_for_listing();
311
 
        home_listing(listing, subjects, path);
312
 
    }
313
 
    else
314
 
    {
315
 
        /* Need to make a 2nd ajax call, this time get the actual file
316
 
         * contents */
317
 
        callback = function(response)
318
 
            {
319
 
                /* Read the response and set up the page accordingly */
320
 
                handle_contents_response(path, response);
321
 
            }
322
 
        /* Call the server and request the listing. */
323
 
        if (url_args)
324
 
            args = shallow_clone_object(url_args);
325
 
        else
326
 
            args = {};
327
 
        /* This time, get the contents of the file, not its metadata */
328
 
        args['return'] = "contents";
329
 
        ajax_call(callback, service_app, path, args, "GET");
330
 
    }
331
 
    update_actions(isdir);
332
 
}
333
 
 
334
 
function handle_contents_response(path, response)
335
 
{
336
 
    /* Treat this as an ordinary file. Get the file type. */
337
 
    var content_type = response.getResponseHeader("Content-Type");
338
 
    var handler_type = get_handler_type(content_type);
339
 
    would_be_handler_type = handler_type;
340
 
    /* handler_type should now be set to either
341
 
     * "text", "image", "audio" or "binary". */
342
 
    switch (handler_type)
343
 
    {
344
 
    case "text":
345
 
        handle_text(path, response.responseText,
346
 
            would_be_handler_type);
347
 
        break;
348
 
    case "image":
349
 
        /* TODO: Custom image handler */
350
 
        handle_binary(path, response.responseText);
351
 
        break;
352
 
    case "audio":
353
 
        /* TODO: Custom audio handler */
354
 
        handle_binary(path, response.responseText);
355
 
        break;
356
 
    case "binary":
357
 
        handle_binary(path);
358
 
        break;
359
 
    }
360
 
}
361
 
 
362
 
/* Called when a form upload comes back (from an iframe).
363
 
 * Refreshes the page.
364
 
 */
365
 
function upload_callback()
366
 
{
367
 
    /* This has a pretty nasty hack, which happens to work.
368
 
     * upload_callback is set as the "onload" callback for the iframe which
369
 
     * receives the response from the server for uploading a file.
370
 
     * This means it gets called twice. Once when initialising the iframe, and
371
 
     * a second time when the actual response comes back.
372
 
     * All we want to do is call navigate to refresh the page. But we CAN'T do
373
 
     * that on the first load or it will just go into an infinite cycle of
374
 
     * refreshing. We need to refresh the page ONLY on the second refresh.
375
 
     * upload_callback_count is reset to 0 just before the iframe is created.
376
 
     */
377
 
    upload_callback_count++;
378
 
    if (upload_callback_count >= 2)
379
 
    {
380
 
        document.getElementsByName('data')[0].value = '';
381
 
        refresh();
382
 
    }
383
 
}
384
 
 
385
 
/** Deletes all "dynamic" content on the page.
386
 
 * This returns the page back to the state it is in when the HTML arrives to
387
 
 * the browser, ready for another handler to populate it.
388
 
 */
389
 
function clearpage()
390
 
{
391
 
    dom_removechildren(document.getElementById("filesbody"));
392
 
}
393
 
 
394
 
/* Checks if a file needs to be saved. If it does, the user will be asked
395
 
 * if they want to continue anyway. The caller must specify a warning
396
 
 * sentence which indicates the consequences of continuing.
397
 
 * Returns true if we should continue, and false if we should not.
398
 
 */
399
 
function maybe_save(warning)
400
 
{
401
 
    if (warning == null) warning = '';
402
 
    if (current_file.isdir) return true;
403
 
    if (document.getElementById("save_button").disabled) return true;
404
 
    return confirm("This file has unsaved changes. " + warning +
405
 
                   "\nAre you sure you wish to continue?");
406
 
}
407
 
 
408
 
/** Deletes all "dynamic" content on the page necessary to navigate from
409
 
 * one directory listing to another (does not clear as much as clearpage
410
 
 * does).
411
 
 * This is the equivalent of calling clearpage() then
412
 
 * setup_for_dir_listing(), assuming the page is already on a dir listing.
413
 
 */
414
 
function clearpage_dir()
415
 
{
416
 
    dom_removechildren(document.getElementById("path"));
417
 
    dom_removechildren(document.getElementById("files"));
418
 
    dom_removechildren(document.getElementById("sidepanel"));
419
 
}
420
 
 
421
 
/*** HANDLERS for different types of responses (such as dir listing, file,
422
 
 * etc). */
423
 
 
424
 
/* handle_error.
425
 
 * message may either be a string, or a DOM node, which will be placed inside
426
 
 * a div.
427
 
 */
428
 
function handle_error(message)
429
 
{
430
 
    var files = document.getElementById("filesbody");
431
 
    var txt_elem;
432
 
    if (typeof(message) == "string")
433
 
    {
434
 
        txt_elem = dom_make_text_elem("div", "Error: "
435
 
                   + message.toString() + ".")
436
 
    }
437
 
    else
438
 
    {
439
 
        /* Assume message is a DOM node */
440
 
        txt_elem = document.createElement("div");
441
 
        txt_elem.appendChild(message);
442
 
    }
443
 
    txt_elem.setAttribute("class", "padding error");
444
 
    files.appendChild(txt_elem);
445
 
}
446
 
 
447
 
/** Given a path, filename and optional revision, returns a URL to open that
448
 
 *  revision of that file.
449
 
 */
450
 
function build_revision_url(path, filename, revision)
451
 
{
452
 
    bits = {'path': app_path(this_app, path, filename)};
453
 
    if (current_revision)
454
 
    {
455
 
        bits['query_string'] = 'r=' + revision;
456
 
    }
457
 
    return build_url(bits);
458
 
}
459
 
 
460
 
/** Given a mime type, returns the path to the icon.
461
 
 * \param type String, Mime type.
462
 
 * \param sizelarge Boolean, optional.
463
 
 * \return Path to the icon. Has applied make_path, so it is relative to site
464
 
 * root.
465
 
 */
466
 
function mime_type_to_icon(type, sizelarge)
467
 
{
468
 
    var filename;
469
 
    if (type in type_icons)
470
 
        filename = type_icons[type];
471
 
    else
472
 
        filename = default_type_icon;
473
 
    if (sizelarge)
474
 
        return make_path(path_join(type_icons_path_large, filename));
475
 
    else
476
 
        return make_path(path_join(type_icons_path, filename));
477
 
}
478
 
 
479
 
/** Given an svnstatus, returns the path to the icon.
480
 
 * \param type String, svn status.
481
 
 * \return Path to the icon. Has applied make_path, so it is relative to site
482
 
 * root. May return null to indicate no SVN icon.
483
 
 */
484
 
function svnstatus_to_icon(svnstatus)
485
 
{
486
 
    var filename;
487
 
    if (svnstatus in svn_icons)
488
 
        filename = svn_icons[svnstatus];
489
 
    else
490
 
        filename = default_svn_icon;
491
 
    if (filename == null) return null;
492
 
    return make_path(path_join(svn_icons_path, filename));
493
 
}
494
 
 
495
 
/** Given an svnstatus, returns the "nice" string.
496
 
 */
497
 
function svnstatus_to_string(svnstatus)
498
 
{
499
 
    if (svnstatus in svn_nice)
500
 
        return svn_nice[svnstatus];
501
 
    else
502
 
        return default_svn_nice;
503
 
}
504
 
 
505
 
/** Displays a download link to the binary file.
506
 
 */
507
 
function handle_binary(path)
508
 
{
509
 
    var files = document.getElementById("filesbody");
510
 
    var div = document.createElement("div");
511
 
    files.appendChild(div);
512
 
    div.setAttribute("class", "padding");
513
 
    var download_link = app_path(download_app, path);
514
 
    var par1 = dom_make_text_elem("p",
515
 
        "The file " + path + " is a binary file. To download this file, " +
516
 
        "click the following link:");
517
 
    var par2 = dom_make_link_elem("p",
518
 
        "Download " + path, "Download " + path, download_link);
519
 
    div.appendChild(par1);
520
 
    div.appendChild(par2);
521
 
}
522
 
 
523
 
/* Enable or disable actions1 moreactions actions. Takes either a single
524
 
 * name, or an array of them.*/
525
 
function set_action_state(names, which, allow_on_revision)
526
 
{
527
 
    if (!(names instanceof Array)) names = Array(names);
528
 
 
529
 
    for (var i=0; i < names.length; i++)
530
 
    {
531
 
        element = document.getElementById('act_' + names[i]);
532
 
        if (which &&
533
 
            !(current_file.svnstatus == 'revision' && !allow_on_revision))
534
 
        {
535
 
            /* Enabling */
536
 
            element.setAttribute("class", "choice");
537
 
            element.removeAttribute("disabled");
538
 
        }
539
 
        else
540
 
        {
541
 
            /* Disabling */
542
 
            element.setAttribute("class", "disabled");
543
 
            element.setAttribute("disabled", "disabled");
544
 
        }
545
 
    }
546
 
}
547
 
 
548
 
function update_actions()
549
 
{
550
 
    var file;
551
 
    var numsel = selected_files.length;
552
 
    if (numsel <= 1)
553
 
    {
554
 
        if (numsel == 0)
555
 
        {
556
 
            /* Display information about the current directory instead */
557
 
            filename = path_basename(current_path);
558
 
            file = current_file;
559
 
        }
560
 
        else if (numsel == 1)
561
 
        {
562
 
            filename = selected_files[0];
563
 
            file = file_listing[filename];
564
 
        }
565
 
 
566
 
        /* Update each action node in the topbar.
567
 
         * This includes enabling/disabling actions as appropriate, and
568
 
         * setting href/onclick attributes. */
569
 
    }
570
 
 
571
 
    /* Open */
572
 
    /* Available if exactly one file is selected */
573
 
    var open = document.getElementById("act_open");
574
 
    if (numsel == 1)
575
 
    {
576
 
        open.setAttribute("class", "choice");
577
 
        if (file.isdir)
578
 
            open.setAttribute("title",
579
 
                "Navigate to this directory in the file browser");
580
 
        else
581
 
            open.setAttribute("title",
582
 
                "Edit or view this file");
583
 
        open.setAttribute("href", build_revision_url(current_path, filename,
584
 
                                                     current_revision));
585
 
    }
586
 
    else
587
 
    {
588
 
        open.setAttribute("class", "disabled");
589
 
        open.removeAttribute("title");
590
 
        open.removeAttribute("href");
591
 
    }
592
 
 
593
 
    /* Serve */
594
 
    /* Available if zero or one files are selected,
595
 
     * and only if this is a file, not a directory */
596
 
    var serve = document.getElementById("act_serve");
597
 
    if (numsel <= 1 && !file.isdir && current_file.svnstatus != 'revision')
598
 
    {
599
 
        serve.setAttribute("class", "choice");
600
 
        serve.setAttribute("onclick",
601
 
              "return maybe_save('The last saved version will be served.')");
602
 
        if (numsel == 0)
603
 
            serve.setAttribute("href",
604
 
                app_path(serve_app, current_path));
605
 
        else
606
 
            serve.setAttribute("href",
607
 
                app_path(serve_app, current_path, filename));
608
 
    }
609
 
    else
610
 
    {
611
 
        serve.setAttribute("class", "disabled");
612
 
        serve.removeAttribute("href");
613
 
        serve.removeAttribute("onclick");
614
 
    }
615
 
 
616
 
    /* Run */
617
 
    /* Available if exactly one file is selected,
618
 
     * and it is a Python file.
619
 
     */
620
 
    var run = document.getElementById("act_run");
621
 
     
622
 
    if (!file.isdir && file.type == "text/x-python" && numsel <= 1
623
 
        && current_file.svnstatus != 'revision')
624
 
    {
625
 
        if (numsel == 0)
626
 
        {
627
 
            // In the edit window
628
 
            var localpath = path_join('/home', current_path);
629
 
        }
630
 
        else
631
 
        {
632
 
            // In the browser window
633
 
            var localpath = path_join('/home', current_path, filename);
634
 
        }
635
 
        run.setAttribute("class", "choice");
636
 
        run.setAttribute("onclick", "runfile('" + localpath + "')");
637
 
    }
638
 
    else
639
 
    {
640
 
        run.setAttribute("class", "disabled");
641
 
        run.removeAttribute("onclick");
642
 
    }
643
 
 
644
 
    /* Download */
645
 
    /* Always available for current files.
646
 
     * If 0 files selected, download the current file or directory as a ZIP.
647
 
     * If 1 directory selected, download it as a ZIP.
648
 
     * If 1 non-directory selected, download it.
649
 
     * If >1 files selected, download them all as a ZIP.
650
 
     */
651
 
    var download = document.getElementById("act_download");
652
 
    if (current_file.svnstatus == 'revision')
653
 
    {
654
 
        download.setAttribute("class", "disabled");
655
 
        download.removeAttribute("onclick");
656
 
    }
657
 
    else if (numsel <= 1)
658
 
    {
659
 
        download.setAttribute("class", "choice")
660
 
        if (numsel == 0)
661
 
        {
662
 
            download.setAttribute("href",
663
 
                app_path(download_app, current_path));
664
 
            if (file.isdir)
665
 
                download.setAttribute("title",
666
 
                    "Download the current directory as a ZIP file");
667
 
            else
668
 
                download.setAttribute("title",
669
 
                    "Download the current file");
670
 
        }
671
 
        else
672
 
        {
673
 
            download.setAttribute("href",
674
 
                app_path(download_app, current_path, filename));
675
 
            if (file.isdir)
676
 
                download.setAttribute("title",
677
 
                    "Download the selected directory as a ZIP file");
678
 
            else
679
 
                download.setAttribute("title",
680
 
                    "Download the selected file");
681
 
        }
682
 
    }
683
 
    else
684
 
    {
685
 
        /* Make a query string with all the files to download */
686
 
        var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
687
 
        for (var i=0; i<numsel; i++)
688
 
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
689
 
        dlpath = dlpath.substr(0, dlpath.length-1);
690
 
        download.setAttribute("class", "choice")
691
 
        download.setAttribute("href", dlpath);
692
 
        download.setAttribute("title",
693
 
            "Download the selected files as a ZIP file");
694
 
    }
695
 
 
696
 
    /* Refresh - No changes required */
697
 
 
698
 
    /* Publish and Submit */
699
 
    /* If this directory is under subversion and selected/unselected file is a
700
 
     * directory. */
701
 
    var publish = document.getElementById("act_publish");
702
 
    var submit = document.getElementById("act_submit");
703
 
    var pubcond = numsel <= 1 && file.isdir;
704
 
    if (pubcond)
705
 
    {
706
 
        /* TODO: Work out of file is svn'd */
707
 
        /* If this dir is already published, call it "Unpublish" */
708
 
        if (file.published)
709
 
        {
710
 
            publish.setAttribute("value", "unpublish");
711
 
            publish.setAttribute("title" ,"Make it so this directory "
712
 
                + "can not be seen by anyone on the web");
713
 
            publish.textContent = "Unpublish";
714
 
        } else {
715
 
            publish.setAttribute("value", "publish");
716
 
            publish.setAttribute("title","Make it so this directory "
717
 
                + "can be seen by anyone on the web");
718
 
            publish.textContent = "Publish";
719
 
        }
720
 
    }
721
 
    set_action_state(["publish", "submit"], pubcond);
722
 
 
723
 
    /* Share */
724
 
    /* If exactly 1 non-directory file is selected, and its parent
725
 
     * directory is published.
726
 
     */
727
 
    set_action_state("share", numsel == 1 && !file.isdir &&
728
 
                     current_file.published);
729
 
 
730
 
    /* Rename */
731
 
    /* If exactly 1 file is selected */
732
 
    set_action_state("rename", numsel == 1);
733
 
 
734
 
    /* Delete, cut, copy */
735
 
    /* If >= 1 file is selected */
736
 
    set_action_state(["delete", "cut", "copy"], numsel >= 1);
737
 
 
738
 
    /* Paste, new file, new directory, upload */
739
 
    /* Disable if the current file is not a directory */
740
 
    set_action_state(["paste", "newfile", "mkdir", "upload"], current_file.isdir);
741
 
 
742
 
    /* Subversion actions */
743
 
    /* These are only useful if we are in a versioned directory and have some
744
 
     * files selected. */
745
 
    set_action_state(["svnadd", "svnremove", "svnrevert", "svncommit"], numsel >= 1 && current_file.svnstatus);
746
 
 
747
 
    /* Diff, log and update only support one path at the moment, so we must
748
 
     * have 0 or 1 versioned files selected. If 0, the directory must be
749
 
     * versioned. */
750
 
    single_versioned_path = (
751
 
         (
752
 
          (numsel == 1 && (svnst = file_listing[selected_files[0]].svnstatus)) ||
753
 
          (numsel == 0 && (svnst = current_file.svnstatus))
754
 
         ) && svnst != "unversioned");
755
 
    set_action_state(["svndiff", "svnupdate"], single_versioned_path);
756
 
 
757
 
    /* We can resolve if we have a file selected and it is conflicted. */
758
 
    set_action_state("svnresolved", single_versioned_path && numsel == 1 && svnst == "conflicted");
759
 
 
760
 
    /* Log should be available for revisions as well. */
761
 
    set_action_state("svnlog", single_versioned_path, true);
762
 
 
763
 
    /* There is currently nothing on the More Actions menu of use
764
 
     * when the current file is not a directory. Hence, just remove
765
 
     * it entirely.
766
 
     * (This makes some of the above decisions somewhat redundant).
767
 
     * We also take this opportunity to show the appropriate actions2
768
 
     * bar for this path. It should either be a save or upload widget.
769
 
     */
770
 
    if (current_file.isdir)
771
 
    {
772
 
        var actions2_directory = document.getElementById("actions2_directory");
773
 
        actions2_directory.setAttribute("style", "display: inline;");
774
 
        var moreactions = document.getElementById("moreactions_area");
775
 
        moreactions.setAttribute("style", "display: inline;");
776
 
    }
777
 
    else
778
 
    {
779
 
        var actions2_file = document.getElementById("actions2_file");
780
 
        actions2_file.setAttribute("style", "display: inline;");
781
 
    }
782
 
 
783
 
    return;
784
 
}
785
 
 
786
 
/** Event handler for when an item of the "More actions..." dropdown box is
787
 
 * selected. Performs the selected action. */
788
 
function handle_moreactions()
789
 
{
790
 
    var moreactions = document.getElementById("moreactions");
791
 
    if (moreactions.value == "top")
792
 
        return;
793
 
    var selectedaction = moreactions.value;
794
 
    /* Reset to "More actions..." */
795
 
    moreactions.selectedIndex = 0;
796
 
 
797
 
    /* If 0 files selected, filename is the name of the current dir.
798
 
     * If 1 file selected, filename is that file.
799
 
     */
800
 
    if (selected_files.length == 0)
801
 
        filename = path_basename(current_path);
802
 
    else if (selected_files.length == 1)
803
 
        filename = selected_files[0];
804
 
    else
805
 
        filename = null;
806
 
 
807
 
    /* Now handle the selected action */
808
 
    switch(selectedaction)
809
 
    {
810
 
    case "publish":
811
 
        action_publish(selected_files);
812
 
        break;
813
 
    case "unpublish":
814
 
        action_unpublish(selected_files);
815
 
        break;
816
 
    case "share":
817
 
        //alert("Not yet implemented: Sharing files");
818
 
        window.open(public_app_path(serve_app, current_path, filename), 'share')
819
 
        break;
820
 
    case "submit":
821
 
        // TODO
822
 
        alert("Not yet implemented: Submit");
823
 
        break;
824
 
    case "rename":
825
 
        action_rename(filename);
826
 
        break;
827
 
    case "delete":
828
 
        action_delete(selected_files);
829
 
        break;
830
 
    case "copy":
831
 
        action_copy(selected_files);
832
 
        break;
833
 
    case "cut":
834
 
        action_cut(selected_files);
835
 
        break;
836
 
    case "paste":
837
 
        action_paste();
838
 
        break;
839
 
    case "newfile":
840
 
        action_newfile();
841
 
        break;
842
 
    case "mkdir":
843
 
        action_mkdir();
844
 
        break;
845
 
    case "upload":
846
 
        show_uploadpanel(true);
847
 
        break;
848
 
    case "svnadd":
849
 
        action_add(selected_files);
850
 
        break;
851
 
    case "svnremove":
852
 
        action_remove(selected_files);
853
 
        break;
854
 
    case "svnrevert":
855
 
        action_revert(selected_files);
856
 
        break;
857
 
    case "svndiff":
858
 
        window.location = path_join(app_path('diff'), current_path, selected_files[0] || '');
859
 
        break;
860
 
    case "svnupdate":
861
 
        action_update(selected_files);
862
 
        break;
863
 
    case "svnresolved":
864
 
        action_resolved(selected_files);
865
 
        break;
866
 
    case "svncommit":
867
 
        action_commit(selected_files);
868
 
        break;
869
 
    case "svnlog":
870
 
        window.location = path_join(app_path('svnlog'), current_path, selected_files[0] || '');
871
 
        break;
872
 
    }
873
 
}
874
 
 
875
 
/** User clicks "Run" button.
876
 
 * Do an Ajax call and print the test output.
877
 
 */
878
 
function runfile(localpath)
879
 
{
880
 
    if (!maybe_save('The last saved version will be run.')) return false;
881
 
 
882
 
    /* Dump the entire file to the console */
883
 
    var callback = function()
884
 
    {
885
 
        console_enter_line("execfile('" + localpath + "')", "block");
886
 
    }
887
 
    start_server(callback)
888
 
    return;
889
 
}
890
 
 
891
 
/** Called when the page loads initially.
892
 
 */
893
 
function browser_init()
894
 
{
895
 
    /* Navigate (internally) to the path in the URL bar.
896
 
     * This causes the page to be populated with whatever is at that address,
897
 
     * whether it be a directory or a file.
898
 
     */
899
 
    var path = parse_url(window.location.href).path;
900
 
    /* Strip out root_dir + "/files" from the front of the path */
901
 
    var strip = make_path(this_app);
902
 
    if (path.substr(0, strip.length) == strip)
903
 
        path = path.substr(strip.length+1);
904
 
    else
905
 
    {
906
 
        /* See if this is an edit path */
907
 
        strip = make_path(edit_app);
908
 
        if (path.substr(0, strip.length) == strip)
909
 
        {
910
 
            path = path.substr(strip.length+1);
911
 
        }
912
 
    }
913
 
 
914
 
    if (path.length == 0)
915
 
    {
916
 
        /* Navigate to the user's home directory by default */
917
 
        /* TEMP? */
918
 
        path = username;
919
 
    }
920
 
 
921
 
    navigate(path);
922
 
}