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

« back to all changes in this revision

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

  • Committer: mattgiuca
  • Date: 2008-01-23 06:55:12 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:276
Console now runs inside IVLE (without requiring an IFRAME). The separate
console server is still spawned, but the client never directly communicates
with it.

apps/console: Writes the HTML code for the actual console (the 2 text boxes),
    instead of just a contained div for the IFRAME.

media/console/console.js:
    Removed code to create IFRAME and link to console server.
    Incorporated Tom's console.js code from trunk/console/console.js.
    Modified this code to:
        a) Use functions from util.js, such as make_query_string and
            ajax_call.
        b) Talk to consoleservice/chat instead of the console server.
        c) Pass host and port as arguments (needed by IVLE to talk to the
            console server).
    Also had the "startup" code save the host, port and magic, needed for
    communication with the console server.

media/console/console.css: Added CSS file (derived from Tom's inline CSS in
    the original index.html).

apps/consoleservice: Fixed bugs which caused the wrong text to be sent, and
    also avoid throwing an error for empty text.

Note that trunk/console will still serve the HTML, JavaScript and CSS files if
needed. The next step is to make this just a service and not actually a web
server as well.

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
    "application/x-javascript" : "text",
44
44
    "application/javascript" : "text",
45
45
    "application/json" : "text",
46
 
    "application/xml" : "text"
 
46
    "application/xml" : "text",
47
47
};
48
48
 
49
49
/* Mapping MIME types to icons, just the file's basename */
50
50
type_icons = {
51
51
    "text/directory": "dir.png",
52
 
    "text/x-python": "py.png"
 
52
    "text/x-python": "py.png",
53
53
};
54
54
 
55
55
default_type_icon = "txt.png";
56
56
 
57
57
/* Relative to IVLE root */
58
 
type_icons_path = "+media/ivle.webapp.core/images/mime";
59
 
type_icons_path_large = "+media/ivle.webapp.core/images/mime/large";
 
58
type_icons_path = "media/images/mime";
 
59
type_icons_path_large = "media/images/mime/large";
60
60
 
61
61
/* Mapping SVN status to icons, just the file's basename */
62
62
svn_icons = {
63
 
    "unversioned": "unversioned.png",
64
 
    "ignored": null,                    /* Supposed to be innocuous */
 
63
    "unversioned": null,
65
64
    "normal": "normal.png",
66
65
    "added": "added.png",
67
66
    "missing": "missing.png",
68
67
    "deleted": "deleted.png",
69
 
    "replaced": "replaced.png",
70
68
    "modified": "modified.png",
71
 
    "conflicted": "conflicted.png",
72
 
    "revision": "revision.png"
73
69
};
74
70
 
75
71
/* Mapping SVN status to "nice" strings */
76
72
svn_nice = {
77
73
    "unversioned": "Temporary file",
78
 
    "ignored": "Temporary file (ignored)",
79
74
    "normal": "Permanent file",
80
75
    "added": "Temporary file (scheduled to be added)",
81
76
    "missing": "Permanent file (missing)",
84
79
    "modified": "Permanent file (modified)",
85
80
    "merged": "Permanent file (merged)",
86
81
    "conflicted": "Permanent file (conflicted)",
87
 
    "revision": "Past Permanent file (revision)"
88
82
};
89
83
 
90
84
default_svn_icon = null;
91
85
default_svn_nice = "Unknown status";
92
86
 
93
 
svn_icons_path = "+media/ivle.webapp.core/images/svn";
 
87
svn_icons_path = "media/images/svn";
94
88
 
95
 
published_icon = "+media/ivle.webapp.core/images/interface/published.png";
 
89
published_icon = "media/images/interface/published.png";
96
90
 
97
91
/* List of MIME types considered "executable" by the system.
98
92
 * Executable files offer a "run" link, implying that the "serve"
99
93
 * application can interpret them.
100
94
 */
101
95
types_exec = [
102
 
    "text/x-python"
 
96
    "text/x-python",
103
97
];
104
98
 
105
99
 
106
100
/* Global variables */
107
101
 
108
 
/** The listing object returned by the server as JSON */
109
 
file_listing = null;
110
 
current_file = null;
111
 
current_revision = null;
112
102
current_path = "";
113
103
 
114
 
/** Filenames of all files selected
115
 
 * (Only used by dir listings, but still needs to be [] for files, so that
116
 
 * update_actions knows that nothing is selected).
117
 
 */
118
 
selected_files = [];
119
 
 
120
 
upload_callback_count = 0;      /* See upload_callback */
121
 
 
122
104
/** Calls the server using Ajax, performing an action on the server side.
123
105
 * Receives the response from the server and performs a refresh of the page
124
106
 * contents, updating it to display the returned data (such as a directory
137
119
 *      May be "application/x-www-form-urlencoded" or "multipart/form-data".
138
120
 *      Defaults to "application/x-www-form-urlencoded".
139
121
 *      "multipart/form-data" is recommended for large uploads.
140
 
 * \param callback, optional.
141
 
 *      A callback function for after the action has been handled.
142
122
 */
143
 
function do_action(action, path, args, content_type, callback)
 
123
function do_action(action, path, args, content_type)
144
124
{
145
125
    args.action = action;
146
 
    /* Callback action, when the server returns */
147
 
    var callback_inner = function(response)
148
 
        {
149
 
            /* Check for action errors reported by the server, and report them
150
 
             * to the user */
151
 
            var error = response.getResponseHeader("X-IVLE-Action-Error");
152
 
            if (error != null && error != "")
153
 
                /* Note: This header (in particular) comes URI-encoded, to
154
 
                 * allow multi-line error messages. Decode */
155
 
                alert("Error: " + decodeURIComponent(error.toString()) + ".");
156
 
            /* Now read the response and set up the page accordingly */
157
 
            if (callback != null)
158
 
                callback(path, response);
159
 
        }
160
126
    /* Call the server and perform the action. This mutates the server. */
161
 
    ajax_call(callback_inner, service_app, path, args, "POST", content_type);
 
127
    response = ajax_call(service_app, path, args, "POST", content_type);
 
128
    /* Check for action errors reported by the server, and report them to the
 
129
     * user */
 
130
    error = response.getResponseHeader("X-IVLE-Action-Error");
 
131
    if (error != null)
 
132
        alert("Error: " + error.toString() + ".");
 
133
    /* Now read the response and set up the page accordingly */
 
134
    handle_response(path, response);
162
135
}
163
136
 
164
137
/** Calls the server using Ajax, requesting a directory listing. This should
168
141
 * Called "navigate", can also be used for a simple refresh.
169
142
 * Always makes a GET request.
170
143
 * No return value.
171
 
 */
172
 
function navigate(path)
173
 
{
174
 
    callback = function(response)
175
 
        {
176
 
            /* Read the response and set up the page accordingly */
177
 
            handle_response(path, response, false, url.args);
178
 
        }
179
 
    /* Get any query strings */
180
 
    url = parse_url(window.location.href);
181
 
    
182
 
    /* Call the server and request the listing. */
183
 
    ajax_call(callback, service_app, path, url.args, "GET");
184
 
}
185
 
 
186
 
/* Refreshes the current view.
187
 
 * Calls navigate on the current path.
188
 
 */
189
 
function refresh()
190
 
{
191
 
    if (maybe_save('All changes since the last save will be lost!'))
192
 
        navigate(current_path);
 
144
 * \param editmode Optional boolean. If true, then the user navigated here
 
145
 * with an "edit" URL so we should favour using the editor.
 
146
 */
 
147
function navigate(path, editmode)
 
148
{
 
149
    /* Call the server and request the listing. This mutates the server. */
 
150
    response = ajax_call(service_app, path, null, "GET");
 
151
    /* Now read the response and set up the page accordingly */
 
152
    handle_response(path, response, editmode);
193
153
}
194
154
 
195
155
/** Determines the "handler type" from a MIME type.
221
181
 * things) be used to update the URL in the location bar.
222
182
 * \param response XMLHttpRequest object returned by the server. Should
223
183
 * contain all the response data.
224
 
 * \param is_action Boolean. True if this is the response to an action, false
225
 
 * if this is the response to a simple listing. This is used in handling the
226
 
 * error.
227
 
 * \param url_args Arguments dict, for the arguments passed to the URL
228
 
 * in the browser's address bar (will be forwarded along).
 
184
 * \param editmode Optional boolean. If true, then the user navigated here
 
185
 * with an "edit" URL so we should favour using the editor.
229
186
 */
230
 
function handle_response(path, response, is_action, url_args)
 
187
function handle_response(path, response, editmode)
231
188
{
232
189
    /* TODO: Set location bar to "path" */
233
190
    current_path = path;
234
191
 
235
192
    /* Clear away the existing page contents */
236
193
    clearpage();
 
194
    /* Display the path at the top, for navigation */
 
195
    presentpath(path);
237
196
 
238
197
    /* Check the status, and if not 200, read the error and handle this as an
239
198
     * error. */
246
205
        return;
247
206
    }
248
207
 
249
 
    var subjects = null;
250
 
    /* Remove trailing slash (or path==username won't compare properly) */
251
 
    if (path[path.length-1] == "/")
252
 
        path = path.substr(0, path.length-1);
253
 
    var top_level_dir = path==username;
254
 
    if (top_level_dir)
255
 
    {
256
 
        var req = ajax_call(null, "userservice", "get_enrolments", null, "GET")
257
 
        subjects = decode_response(req);
258
 
    }
259
 
 
260
 
 
261
 
    /* This will always return a listing, whether it is a dir or a file.
262
 
     */
263
 
    var listing = response.responseText;
264
 
    /* The listing SHOULD be valid JSON text. Parse it into an object. */
265
 
    try
266
 
    {
267
 
        listing = JSON.parse(listing);
268
 
        file_listing = listing.listing;     /* Global */
269
 
    }
270
 
    catch (e)
271
 
    {
272
 
        if (is_action)
273
 
        {
274
 
            var err = document.createElement("div");
275
 
            var p = dom_make_text_elem("p", "Error: "
276
 
                    + "There was an unexpected server error processing "
277
 
                    + "the selected command.");
278
 
            err.appendChild(p);
279
 
            p = dom_make_text_elem("p", "If the problem persists, please "
280
 
                    + "contact the system administrator.")
281
 
            err.appendChild(p);
282
 
            p = document.createElement("p");
283
 
            var refresh = document.createElement("input");
284
 
            refresh.setAttribute("type", "button");
285
 
            refresh.setAttribute("value", "Back to file view");
286
 
            refresh.setAttribute("onclick", "refresh()");
287
 
            p.appendChild(refresh);
288
 
            err.appendChild(p);
289
 
            handle_error(err);
290
 
        }
291
 
        else
292
 
        {
293
 
            var err = document.createElement("div");
294
 
            var p = dom_make_text_elem("p", "Error: "
295
 
                    + "There was an unexpected server error retrieving "
296
 
                    + "the requested file or directory.");
297
 
            err.appendChild(p);
298
 
            p = dom_make_text_elem("p", "If the problem persists, please "
299
 
                    + "contact the system administrator.")
300
 
            err.appendChild(p);
301
 
            handle_error(err);
302
 
        }
303
 
        return;
304
 
    }
305
 
    /* Get "." out, it's special */
306
 
    current_file = file_listing["."];     /* Global */
307
 
    delete file_listing["."];
308
 
 
309
 
    if ('revision' in listing)
310
 
    {
311
 
        current_revision = listing.revision;
312
 
    }
313
 
 
314
208
    /* Check if this is a directory listing or file contents */
315
209
    var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
316
 
    if (isdir)
 
210
    if (!editmode && isdir)
317
211
    {
318
 
        setup_for_listing();
319
 
        if (top_level_dir)
320
 
        {
321
 
            /* Top-level dir, with subjects */
322
 
            special_home_listing(listing, subjects, path);
323
 
        }
324
 
        else
325
 
        {
326
 
            /* Not the top-level dir. Do a normal dir listing. */
327
 
            handle_dir_listing(path, listing.listing);
328
 
        }
 
212
        var listing = response.responseText;
 
213
        /* The listing SHOULD be valid JSON text. Parse it into an object. */
 
214
        try
 
215
        {
 
216
            listing = JSON.parse(listing);
 
217
        }
 
218
        catch (e)
 
219
        {
 
220
            handle_error("The server returned an invalid directory listing");
 
221
            return;
 
222
        }
 
223
        handle_dir_listing(path, listing);
329
224
    }
330
225
    else
331
226
    {
332
 
        /* Need to make a 2nd ajax call, this time get the actual file
333
 
         * contents */
334
 
        callback = function(response)
335
 
            {
336
 
                /* Read the response and set up the page accordingly */
337
 
                handle_contents_response(path, response);
338
 
            }
339
 
        /* Call the server and request the listing. */
340
 
        if (url_args)
341
 
            args = shallow_clone_object(url_args);
342
 
        else
343
 
            args = {};
344
 
        /* This time, get the contents of the file, not its metadata */
345
 
        args['return'] = "contents";
346
 
        ajax_call(callback, service_app, path, args, "GET");
347
 
    }
348
 
    update_actions(isdir);
349
 
}
350
 
 
351
 
function handle_contents_response(path, response)
352
 
{
353
 
    /* Treat this as an ordinary file. Get the file type. */
354
 
    var content_type = response.getResponseHeader("Content-Type");
355
 
    var handler_type = get_handler_type(content_type);
356
 
    would_be_handler_type = handler_type;
357
 
    /* handler_type should now be set to either
358
 
     * "text", "image", "audio" or "binary". */
359
 
    switch (handler_type)
360
 
    {
361
 
    case "text":
362
 
        handle_text(path, response.responseText,
363
 
            would_be_handler_type);
364
 
        break;
365
 
    case "image":
366
 
        /* TODO: Custom image handler */
367
 
        handle_binary(path, response.responseText);
368
 
        break;
369
 
    case "audio":
370
 
        /* TODO: Custom audio handler */
371
 
        handle_binary(path, response.responseText);
372
 
        break;
373
 
    case "binary":
374
 
        handle_binary(path);
375
 
        break;
376
 
    }
377
 
}
378
 
 
379
 
/* Called when a form upload comes back (from an iframe).
380
 
 * Refreshes the page.
381
 
 */
382
 
function upload_callback()
383
 
{
384
 
    /* This has a pretty nasty hack, which happens to work.
385
 
     * upload_callback is set as the "onload" callback for the iframe which
386
 
     * receives the response from the server for uploading a file.
387
 
     * This means it gets called twice. Once when initialising the iframe, and
388
 
     * a second time when the actual response comes back.
389
 
     * All we want to do is call navigate to refresh the page. But we CAN'T do
390
 
     * that on the first load or it will just go into an infinite cycle of
391
 
     * refreshing. We need to refresh the page ONLY on the second refresh.
392
 
     * upload_callback_count is reset to 0 just before the iframe is created.
393
 
     */
394
 
    upload_callback_count++;
395
 
    if (upload_callback_count >= 2)
396
 
    {
397
 
        myFrame = frames['upload_iframe'].document;
398
 
        /* Browsers will turn the raw returned JSON into an HTML document. We
399
 
         * need to get the <pre> from inside the <body>, and look at its text.
400
 
         */
401
 
        var pre = myFrame.firstChild.getElementsByTagName(
402
 
            'body')[0].firstChild;
403
 
        var data = pre.innerText || pre.textContent;
404
 
        data = JSON.parse(data);
405
 
        if ('Error' in data)
406
 
            alert("Error: " + decodeURIComponent(data['Error']));
407
 
        document.getElementsByName('data')[0].value = '';
408
 
        refresh();
 
227
        /* Treat this as an ordinary file. Get the file type. */
 
228
        var content_type = response.getResponseHeader("Content-Type");
 
229
        var handler_type = get_handler_type(content_type);
 
230
        /* If we're in "edit mode", always treat this file as text */
 
231
        would_be_handler_type = handler_type;
 
232
        if (editmode) handler_type = "text";
 
233
        /* handler_type should now be set to either
 
234
         * "text", "image", "audio" or "binary". */
 
235
        switch (handler_type)
 
236
        {
 
237
        case "text":
 
238
            if (isdir)
 
239
            {
 
240
                handle_text(path_join(path, "untitled"), "",
 
241
                    would_be_handler_type);
 
242
            }
 
243
            else
 
244
            {
 
245
                handle_text(path, response.responseText,
 
246
                    would_be_handler_type);
 
247
            }
 
248
            break;
 
249
        case "image":
 
250
            /* TODO: Custom image handler */
 
251
            handle_binary(path, response.responseText);
 
252
            break;
 
253
        case "audio":
 
254
            /* TODO: Custom audio handler */
 
255
            handle_binary(path, response.responseText);
 
256
            break;
 
257
        case "binary":
 
258
            handle_binary(path);
 
259
            break;
 
260
        }
409
261
    }
410
262
}
411
263
 
415
267
 */
416
268
function clearpage()
417
269
{
 
270
    dom_removechildren(document.getElementById("path"));
418
271
    dom_removechildren(document.getElementById("filesbody"));
419
272
}
420
273
 
421
 
/* Checks if a file needs to be saved. If it does, the user will be asked
422
 
 * if they want to continue anyway. The caller must specify a warning
423
 
 * sentence which indicates the consequences of continuing.
424
 
 * Returns true if we should continue, and false if we should not.
425
 
 */
426
 
function maybe_save(warning)
427
 
{
428
 
    if (warning == null) warning = '';
429
 
    if (current_file == null || current_file.isdir) return true;
430
 
    if (document.getElementById("save_button").disabled) return true;
431
 
    return confirm("This file has unsaved changes. " + warning +
432
 
                   "\nAre you sure you wish to continue?");
433
 
}
434
 
 
435
274
/** Deletes all "dynamic" content on the page necessary to navigate from
436
275
 * one directory listing to another (does not clear as much as clearpage
437
276
 * does).
445
284
    dom_removechildren(document.getElementById("sidepanel"));
446
285
}
447
286
 
 
287
/** Sets the mode to either "file browser" or "text editor" mode.
 
288
 * This modifies the window icon, and selected tab.
 
289
 * \param editmode If True, editor mode. Else, file browser mode.
 
290
 */
 
291
function setmode(editmode)
 
292
{
 
293
    /* Find the DOM elements for the file browser and editor tabs */
 
294
    var tabs = document.getElementById("apptabs");
 
295
    var tab_files = null;
 
296
    var tab_edit = null;
 
297
    var a;
 
298
    var href;
 
299
    for (var i=0; i<tabs.childNodes.length; i++)
 
300
    {
 
301
        /* Find the href of the link within */
 
302
        if (!tabs.childNodes[i].getElementsByTagName) continue;
 
303
        a = tabs.childNodes[i].getElementsByTagName("a");
 
304
        if (a.length == 0) continue;
 
305
        href = a[0].getAttribute("href");
 
306
        if (href == null) continue;
 
307
        if (endswith(href, this_app))
 
308
            tab_files = tabs.childNodes[i];
 
309
        else if (endswith(href, edit_app))
 
310
            tab_edit = tabs.childNodes[i];
 
311
    }
 
312
 
 
313
    if (editmode)
 
314
    {
 
315
        tab_files.removeAttribute("class");
 
316
        tab_edit.setAttribute("class", "thisapp");
 
317
    }
 
318
    else
 
319
    {
 
320
        tab_edit.removeAttribute("class");
 
321
        tab_files.setAttribute("class", "thisapp");
 
322
    }
 
323
}
 
324
 
448
325
/*** HANDLERS for different types of responses (such as dir listing, file,
449
326
 * etc). */
450
327
 
451
 
/* handle_error.
452
 
 * message may either be a string, or a DOM node, which will be placed inside
453
 
 * a div.
454
 
 */
455
328
function handle_error(message)
456
329
{
 
330
    setmode(false);
457
331
    var files = document.getElementById("filesbody");
458
 
    var txt_elem;
459
 
    if (typeof(message) == "string")
460
 
    {
461
 
        txt_elem = dom_make_text_elem("div", "Error: "
462
 
                   + message.toString() + ".")
463
 
    }
464
 
    else
465
 
    {
466
 
        /* Assume message is a DOM node */
467
 
        txt_elem = document.createElement("div");
468
 
        txt_elem.appendChild(message);
469
 
    }
 
332
    var txt_elem = dom_make_text_elem("div", "Error: "
 
333
        + message.toString() + ".")
470
334
    txt_elem.setAttribute("class", "padding error");
471
335
    files.appendChild(txt_elem);
472
336
}
473
337
 
474
 
/** Given a path, filename and optional revision, returns a URL to open that
475
 
 *  revision of that file.
 
338
/** Presents a path list (address bar inside the page) for clicking.
476
339
 */
477
 
function build_revision_url(path, filename, revision)
 
340
function presentpath(path)
478
341
{
479
 
    bits = {'path': app_path(this_app, path, filename)};
480
 
    if (current_revision)
 
342
    var dom_path = document.getElementById("path");
 
343
    var href_path = make_path(this_app);
 
344
    var nav_path = "";
 
345
    var dir;
 
346
 
 
347
    /* Also set the document title */
 
348
    document.title = path_basename(path) + " - IVLE";
 
349
    /* Create all of the paths */
 
350
    var pathlist = path.split("/");
 
351
    for (var i=0; i<pathlist.length; i++)
481
352
    {
482
 
        bits['query_string'] = 'r=' + revision;
 
353
        dir = pathlist[i];
 
354
        if (dir == "") continue;
 
355
        /* Make an 'a' element */
 
356
        href_path = path_join(href_path, dir);
 
357
        nav_path = path_join(nav_path, dir);
 
358
        var link = dom_make_link_elem("a", dir, "Navigate to " + nav_path,
 
359
                href_path/*, "navigate(" + repr(href_path) + ")"*/);
 
360
        dom_path.appendChild(link);
 
361
        dom_path.appendChild(document.createTextNode("/"));
483
362
    }
484
 
    return build_url(bits);
 
363
    dom_path.removeChild(dom_path.lastChild);
485
364
}
486
365
 
487
366
/** Given a mime type, returns the path to the icon.
529
408
        return default_svn_nice;
530
409
}
531
410
 
532
 
/** Returns true if a file is versioned (not unversioned or ignored).
533
 
 */
534
 
function svnstatus_versioned(svnstatus)
535
 
{
536
 
    return svnstatus != "unversioned" && svnstatus != "ignored";
537
 
}
538
 
 
539
411
/** Displays a download link to the binary file.
540
412
 */
541
413
function handle_binary(path)
542
414
{
 
415
    setmode(false);
543
416
    var files = document.getElementById("filesbody");
544
417
    var div = document.createElement("div");
545
418
    files.appendChild(div);
546
419
    div.setAttribute("class", "padding");
547
 
    var download_link = app_url(download_app, path);
 
420
    var download_link = app_path(download_app, path);
548
421
    var par1 = dom_make_text_elem("p",
549
422
        "The file " + path + " is a binary file. To download this file, " +
550
423
        "click the following link:");
554
427
    div.appendChild(par2);
555
428
}
556
429
 
557
 
/* Enable or disable actions1 moreactions actions. Takes either a single
558
 
 * name, or an array of them.*/
559
 
function set_action_state(names, which, allow_on_revision)
560
 
{
561
 
    if (!(names instanceof Array)) names = Array(names);
562
 
 
563
 
    for (var i=0; i < names.length; i++)
564
 
    {
565
 
        element = document.getElementById('act_' + names[i]);
566
 
        if (which &&
567
 
            !(current_file.svnstatus == 'revision' && !allow_on_revision))
568
 
        {
569
 
            /* Enabling */
570
 
            element.setAttribute("class", "choice");
571
 
            element.removeAttribute("disabled");
572
 
        }
573
 
        else
574
 
        {
575
 
            /* Disabling */
576
 
            element.setAttribute("class", "disabled");
577
 
            element.setAttribute("disabled", "disabled");
578
 
        }
579
 
    }
580
 
}
581
 
 
582
 
/* Updates the list of available actions based on files selected */
583
 
function update_actions()
584
 
{
585
 
    var file;
586
 
    var numsel = selected_files.length;
587
 
    var svn_selection = false;
588
 
    
589
 
    if (numsel > 0)
590
 
    {
591
 
        svn_selection = true;
592
 
        for (var i = 0; i < selected_files.length; i++){
593
 
            if (!svnstatus_versioned(file_listing[selected_files[i]].svnstatus))
594
 
            {
595
 
                svn_selection = false;
596
 
            }
597
 
        }
598
 
    }
599
 
    
600
 
    if (numsel <= 1)
601
 
    {
602
 
        if (numsel == 0)
603
 
        {
604
 
            /* Display information about the current directory instead */
605
 
            filename = path_basename(current_path);
606
 
            file = current_file;
607
 
        }
608
 
        else if (numsel == 1)
609
 
        {
610
 
            filename = selected_files[0];
611
 
            file = file_listing[filename];
612
 
        }
613
 
 
614
 
        /* Update each action node in the topbar.
615
 
         * This includes enabling/disabling actions as appropriate, and
616
 
         * setting href/onclick attributes. */
617
 
    }
618
 
 
619
 
    /* Open */
620
 
    /* Available if exactly one file is selected */
621
 
    var open = document.getElementById("act_open");
622
 
    if (numsel == 1)
623
 
    {
624
 
        open.setAttribute("class", "choice");
625
 
        if (file.isdir)
626
 
            open.setAttribute("title",
627
 
                "Navigate to this directory in the file browser");
628
 
        else
629
 
            open.setAttribute("title",
630
 
                "Edit or view this file");
631
 
        open.setAttribute("href", build_revision_url(current_path, filename,
632
 
                                                     current_revision));
633
 
    }
634
 
    else
635
 
    {
636
 
        open.setAttribute("class", "disabled");
637
 
        open.removeAttribute("title");
638
 
        open.removeAttribute("href");
639
 
    }
640
 
 
641
 
    /* Serve */
642
 
    /* Available if zero or one files are selected,
643
 
     * and only if this is a file, not a directory */
644
 
    var serve = document.getElementById("act_serve");
645
 
    if (numsel <= 1 && !file.isdir && current_file.svnstatus != 'revision')
646
 
    {
647
 
        serve.setAttribute("class", "choice");
648
 
        serve.setAttribute("onclick",
649
 
              "return maybe_save('The last saved version will be served.')");
650
 
        if (numsel == 0)
651
 
            serve.setAttribute("href",
652
 
                app_url(serve_app, current_path));
653
 
        else
654
 
            serve.setAttribute("href",
655
 
                app_url(serve_app, current_path, filename));
656
 
    }
657
 
    else
658
 
    {
659
 
        serve.setAttribute("class", "disabled");
660
 
        serve.removeAttribute("href");
661
 
        serve.removeAttribute("onclick");
662
 
    }
663
 
 
664
 
    /* Run */
665
 
    /* Available if exactly one file is selected,
666
 
     * and it is a Python file.
667
 
     */
668
 
    var run = document.getElementById("act_run");
669
 
     
670
 
    if (numsel <= 1 && !file.isdir && file.type == "text/x-python" 
671
 
            && current_file.svnstatus != 'revision')
672
 
    {
673
 
        if (numsel == 0)
674
 
        {
675
 
            // In the edit window
676
 
            var localpath = path_join('/home', current_path);
677
 
        }
678
 
        else
679
 
        {
680
 
            // In the browser window
681
 
            var localpath = path_join('/home', current_path, filename);
682
 
        }
683
 
        run.setAttribute("class", "choice");
684
 
        run.setAttribute("onclick", "runfile('" + localpath + "')");
685
 
    }
686
 
    else
687
 
    {
688
 
        run.setAttribute("class", "disabled");
689
 
        run.removeAttribute("onclick");
690
 
    }
691
 
 
692
 
    /* Download */
693
 
    /* Always available for current files.
694
 
     * If 0 files selected, download the current file or directory as a ZIP.
695
 
     * If 1 directory selected, download it as a ZIP.
696
 
     * If 1 non-directory selected, download it.
697
 
     * If >1 files selected, download them all as a ZIP.
698
 
     */
699
 
    var download = document.getElementById("act_download");
700
 
    if (current_file.svnstatus == 'revision')
701
 
    {
702
 
        download.setAttribute("class", "disabled");
703
 
        download.removeAttribute("onclick");
704
 
    }
705
 
    else if (numsel <= 1)
706
 
    {
707
 
        download.setAttribute("class", "choice")
708
 
        if (numsel == 0)
709
 
        {
710
 
            download.setAttribute("href",
711
 
                app_url(download_app, current_path));
712
 
            if (file.isdir)
713
 
                download.setAttribute("title",
714
 
                    "Download the current directory as a ZIP file");
715
 
            else
716
 
                download.setAttribute("title",
717
 
                    "Download the current file");
718
 
        }
719
 
        else
720
 
        {
721
 
            download.setAttribute("href",
722
 
                app_url(download_app, current_path, filename));
723
 
            if (file.isdir)
724
 
                download.setAttribute("title",
725
 
                    "Download the selected directory as a ZIP file");
726
 
            else
727
 
                download.setAttribute("title",
728
 
                    "Download the selected file");
729
 
        }
730
 
    }
731
 
    else
732
 
    {
733
 
        /* Make a query string with all the files to download */
734
 
        var dlpath = app_url(download_app, current_path) + "?";
735
 
        for (var i=0; i<numsel; i++)
736
 
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
737
 
        dlpath = dlpath.substr(0, dlpath.length-1);
738
 
        download.setAttribute("class", "choice")
739
 
        download.setAttribute("href", dlpath);
740
 
        download.setAttribute("title",
741
 
            "Download the selected files as a ZIP file");
742
 
    }
743
 
 
744
 
    /* Refresh - No changes required */
745
 
 
746
 
    /* Publish and Submit */
747
 
    /* If this directory is under subversion and selected/unselected file is a
748
 
     * directory. */
749
 
    var publish = document.getElementById("act_publish");
750
 
    var submit = document.getElementById("act_submit");
751
 
    var pubcond = numsel <= 1 && file.isdir;
752
 
    if (pubcond)
753
 
    {
754
 
        /* If this dir is already published, call it "Unpublish" */
755
 
        if (file.published)
756
 
        {
757
 
            publish.setAttribute("value", "unpublish");
758
 
            publish.setAttribute("title" ,"Make it so this directory "
759
 
                + "can not be seen by anyone on the web");
760
 
            publish.firstChild.nodeValue = "Unpublish";
761
 
        } else {
762
 
            publish.setAttribute("value", "publish");
763
 
            publish.setAttribute("title","Make it so this directory "
764
 
                + "can be seen by anyone on the web");
765
 
            publish.firstChild.nodeValue = "Publish";
766
 
        }
767
 
    }
768
 
    set_action_state(["publish", "submit"], pubcond);
769
 
 
770
 
    /* Share */
771
 
    /* If exactly 1 non-directory file is selected, and its parent
772
 
     * directory is published.
773
 
     */
774
 
    set_action_state("share", numsel == 1 && !file.isdir &&
775
 
                     current_file.published);
776
 
 
777
 
    /* Rename */
778
 
    /* If exactly 1 file is selected */
779
 
    set_action_state("rename", numsel == 1);
780
 
 
781
 
    /* Delete, cut, copy */
782
 
    /* If >= 1 file is selected */
783
 
    set_action_state(["delete", "cut", "copy"], numsel >= 1);
784
 
 
785
 
    /* Paste, new file, new directory, upload */
786
 
    /* Disable if the current file is not a directory */
787
 
    set_action_state(["paste", "newfile", "mkdir", "upload"], current_file.isdir);
788
 
 
789
 
    /* Subversion actions */
790
 
    /* These are only useful if we are in a versioned directory and have some
791
 
     * files selected. */
792
 
    set_action_state(["svnrename"], numsel == 1 && current_file.svnstatus);
793
 
    set_action_state(["svnadd"], numsel >= 1 && current_file.svnstatus);
794
 
    /* And these are only useful is ALL the selected files are versioned */
795
 
    set_action_state(["svnremove", "svnrevert", "svncopy", "svncut"],
796
 
            numsel >= 1 && current_file.svnstatus && svn_selection);
797
 
    /* Commit is useful if ALL selected files are versioned, or the current
798
 
     * directory is versioned */
799
 
    set_action_state(["svncommit"], current_file.svnstatus &&
800
 
            (numsel >= 1 && svn_selection || numsel == 0));
801
 
 
802
 
    /* Diff, log and update only support one path at the moment, so we must
803
 
     * have 0 or 1 versioned files selected. If 0, the directory must be
804
 
     * versioned. */
805
 
    single_versioned_path = (
806
 
         (
807
 
          (numsel == 1 && (svnst = file_listing[selected_files[0]].svnstatus)) ||
808
 
          (numsel == 0 && (svnst = current_file.svnstatus))
809
 
         ) && svnstatus_versioned(svnst));
810
 
    set_action_state(["svndiff", "svnupdate"], single_versioned_path);
811
 
 
812
 
    /* We can resolve if we have a file selected and it is conflicted. */
813
 
    set_action_state("svnresolved", single_versioned_path && numsel == 1 && svnst == "conflicted");
814
 
 
815
 
    /* Log should be available for revisions as well. */
816
 
    set_action_state("svnlog", single_versioned_path, true);
817
 
 
818
 
    /* Cleanup should be available for revisions as well. */
819
 
    set_action_state("svncleanup", single_versioned_path, true);
820
 
 
821
 
    single_ivle_versioned_path = (
822
 
         (
823
 
          (numsel == 1 && (stat = file_listing[selected_files[0]])) ||
824
 
          (numsel == 0 && (stat = current_file))
825
 
         ) && svnstatus_versioned(stat.svnstatus)
826
 
           && stat.svnurl
827
 
           && stat.svnurl.substr(0, svn_base.length) == svn_base);
828
 
    set_action_state(["submit"], single_ivle_versioned_path);
829
 
 
830
 
    /* There is currently nothing on the More Actions menu of use
831
 
     * when the current file is not a directory. Hence, just remove
832
 
     * it entirely.
833
 
     * (This makes some of the above decisions somewhat redundant).
834
 
     * We also take this opportunity to show the appropriate actions2
835
 
     * bar for this path. It should either be a save or upload widget.
836
 
     */
837
 
    if (current_file.isdir)
838
 
    {
839
 
        var actions2_directory = document.getElementById("actions2_directory");
840
 
        actions2_directory.setAttribute("style", "display: inline;");
841
 
        var moreactions = document.getElementById("moreactions_area");
842
 
        moreactions.setAttribute("style", "display: inline;");
843
 
    }
844
 
    else
845
 
    {
846
 
        var actions2_file = document.getElementById("actions2_file");
847
 
        actions2_file.setAttribute("style", "display: inline;");
848
 
    }
849
 
 
850
 
    return;
851
 
}
852
 
 
853
 
/** Event handler for when an item of the "More actions..." dropdown box is
854
 
 * selected. Performs the selected action. */
855
 
function handle_moreactions()
856
 
{
857
 
    var moreactions = document.getElementById("moreactions");
858
 
    if (moreactions.value == "top")
859
 
        return;
860
 
    var selectedaction = moreactions.value;
861
 
    /* Reset to "More actions..." */
862
 
    moreactions.selectedIndex = 0;
863
 
 
864
 
    /* If 0 files selected, filename is the name of the current dir.
865
 
     * If 1 file selected, filename is that file.
866
 
     */
867
 
    if (selected_files.length == 0)
868
 
        filename = path_basename(current_path);
869
 
    else if (selected_files.length == 1)
870
 
        filename = selected_files[0];
871
 
    else
872
 
        filename = null;
873
 
 
874
 
    /* Now handle the selected action */
875
 
    switch(selectedaction)
876
 
    {
877
 
    case "publish":
878
 
        action_publish(selected_files);
879
 
        break;
880
 
    case "unpublish":
881
 
        action_unpublish(selected_files);
882
 
        break;
883
 
    case "share":
884
 
        window.open(public_app_url("~" + current_path, filename), 'share')
885
 
        break;
886
 
    case "submit":
887
 
        if (selected_files.length == 1)
888
 
            stat = file_listing[selected_files[0]];
889
 
        else
890
 
            stat = current_file;
891
 
        url = stat.svnurl.substr(svn_base.length);      // URL-encoded
892
 
        path = decodeURIComponent(url);
893
 
 
894
 
        /* The working copy might not have an up-to-date version of the
895
 
         * directory. While submitting like this could yield unexpected
896
 
         * results, we should really submit the latest revision to minimise
897
 
         * terrible mistakes - so we run off and ask fileservice for the
898
 
         * latest revision.*/
899
 
        $.post(app_path(service_app, current_path),
900
 
            {"action": "svnrepostat", "path": path},
901
 
            function(result)
902
 
            {
903
 
                window.location = path_join(app_path('+submit'), url) + '?revision=' + result.svnrevision;
904
 
            },
905
 
            "json");
906
 
 
907
 
        break;
908
 
    case "rename":
909
 
        action_rename(filename);
910
 
        break;
911
 
    case "delete":
912
 
        action_delete(selected_files);
913
 
        break;
914
 
    case "copy":
915
 
        action_copy(selected_files);
916
 
        break;
917
 
    case "cut":
918
 
        action_cut(selected_files);
919
 
        break;
920
 
    case "paste":
921
 
        action_paste();
922
 
        break;
923
 
    case "newfile":
924
 
        action_newfile();
925
 
        break;
926
 
    case "mkdir":
927
 
        action_mkdir();
928
 
        break;
929
 
    case "upload":
930
 
        show_uploadpanel(true);
931
 
        break;
932
 
    case "svnadd":
933
 
        action_add(selected_files);
934
 
        break;
935
 
    case "svnremove":
936
 
        action_svnremove(selected_files);
937
 
        break;
938
 
    case "svnrename":
939
 
        action_svnrename(selected_files);
940
 
        break;
941
 
    case "svnrevert":
942
 
        action_revert(selected_files);
943
 
        break;
944
 
    case "svndiff":
945
 
        window.location = path_join(app_url('diff'), current_path, selected_files[0] || '');
946
 
        break;
947
 
    case "svnupdate":
948
 
        action_update(selected_files);
949
 
        break;
950
 
    case "svnresolved":
951
 
        action_resolved(selected_files);
952
 
        break;
953
 
    case "svncommit":
954
 
        action_commit(selected_files);
955
 
        break;
956
 
    case "svnlog":
957
 
        window.location = path_join(app_url('svnlog'), current_path, selected_files[0] || '');
958
 
        break;
959
 
    case "svncopy":
960
 
        action_svncopy(selected_files);
961
 
        break;
962
 
    case "svncut":
963
 
        action_svncut(selected_files);
964
 
        break;
965
 
    case "svncleanup":
966
 
        action_svncleanup(".");
967
 
        break;
968
 
    }
969
 
}
970
 
 
971
 
/** User clicks "Run" button.
972
 
 * Do an Ajax call and print the test output.
973
 
 */
974
 
function runfile(localpath)
975
 
{
976
 
    if (!maybe_save('The last saved version will be run.')) return false;
977
 
 
978
 
    /* Dump the entire file to the console */
979
 
    var callback = function()
980
 
    {
981
 
        console_enter_line("execfile('" + localpath + "')", "block");
982
 
    }
983
 
    start_server(callback)
984
 
    return;
985
 
}
986
 
 
987
430
/** Called when the page loads initially.
988
431
 */
989
 
function browser_init()
 
432
window.onload = function()
990
433
{
991
434
    /* Navigate (internally) to the path in the URL bar.
992
435
     * This causes the page to be populated with whatever is at that address,
993
436
     * whether it be a directory or a file.
994
437
     */
995
 
    var path = get_path();
996
 
    navigate(path);
997
 
}
998
 
 
999
 
/** Gets the current path of the window */
1000
 
function get_path() {
1001
438
    var path = parse_url(window.location.href).path;
1002
439
    /* Strip out root_dir + "/files" from the front of the path */
1003
440
    var strip = make_path(this_app);
 
441
    var editmode = false;
1004
442
    if (path.substr(0, strip.length) == strip)
1005
443
        path = path.substr(strip.length+1);
1006
444
    else
1010
448
        if (path.substr(0, strip.length) == strip)
1011
449
        {
1012
450
            path = path.substr(strip.length+1);
 
451
            editmode = true;
1013
452
        }
1014
453
    }
1015
454
 
1020
459
        path = username;
1021
460
    }
1022
461
 
1023
 
    return path;
 
462
    navigate(path, editmode);
1024
463
}