~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-02-05 01:41:15 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:409
Moved www/conf and www/common to a new directory lib. This separates the "web"
part of IVLE from what is becoming less web oriented (at least from Apache's
standpoint).
Modified setup.py to install this lib directory correctly and write conf in
the right place. Also adds the lib directory to ivle.pth.

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 = {
66
66
    "missing": "missing.png",
67
67
    "deleted": "deleted.png",
68
68
    "modified": "modified.png",
69
 
    "conflicted": "conflicted.png",
70
 
    "revision": "revision.png"
71
69
};
72
70
 
73
71
/* Mapping SVN status to "nice" strings */
81
79
    "modified": "Permanent file (modified)",
82
80
    "merged": "Permanent file (merged)",
83
81
    "conflicted": "Permanent file (conflicted)",
84
 
    "revision": "Past Permanent file (revision)"
85
82
};
86
83
 
87
84
default_svn_icon = null;
88
85
default_svn_nice = "Unknown status";
89
86
 
90
 
svn_icons_path = "+media/ivle.webapp.core/images/svn";
 
87
svn_icons_path = "media/images/svn";
91
88
 
92
 
published_icon = "+media/ivle.webapp.core/images/interface/published.png";
 
89
published_icon = "media/images/interface/published.png";
93
90
 
94
91
/* List of MIME types considered "executable" by the system.
95
92
 * Executable files offer a "run" link, implying that the "serve"
96
93
 * application can interpret them.
97
94
 */
98
95
types_exec = [
99
 
    "text/x-python"
 
96
    "text/x-python",
100
97
];
101
98
 
102
99
 
103
100
/* Global variables */
104
101
 
105
 
/** The listing object returned by the server as JSON */
106
 
file_listing = null;
107
 
current_file = null;
108
 
current_revision = null;
109
102
current_path = "";
110
103
 
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
104
/** Calls the server using Ajax, performing an action on the server side.
120
105
 * Receives the response from the server and performs a refresh of the page
121
106
 * contents, updating it to display the returned data (such as a directory
138
123
function do_action(action, path, args, content_type, ignore_response)
139
124
{
140
125
    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 && error != "")
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
126
    /* Call the server and perform the action. This mutates the server. */
156
 
    ajax_call(callback, 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
    if (ignore_response != true)
 
135
        handle_response(path, response);
157
136
}
158
137
 
159
138
/** Calls the server using Ajax, requesting a directory listing. This should
163
142
 * Called "navigate", can also be used for a simple refresh.
164
143
 * Always makes a GET request.
165
144
 * 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);
 
145
 * \param editmode Optional boolean. If true, then the user navigated here
 
146
 * with an "edit" URL so we should favour using the editor.
 
147
 */
 
148
function navigate(path, editmode)
 
149
{
 
150
    /* Call the server and request the listing. This mutates the server. */
 
151
    response = ajax_call(service_app, path, null, "GET");
 
152
    /* Now read the response and set up the page accordingly */
 
153
    handle_response(path, response, editmode);
188
154
}
189
155
 
190
156
/** Determines the "handler type" from a MIME type.
216
182
 * things) be used to update the URL in the location bar.
217
183
 * \param response XMLHttpRequest object returned by the server. Should
218
184
 * 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).
 
185
 * \param editmode Optional boolean. If true, then the user navigated here
 
186
 * with an "edit" URL so we should favour using the editor.
224
187
 */
225
 
function handle_response(path, response, is_action, url_args)
 
188
function handle_response(path, response, editmode)
226
189
{
227
190
    /* TODO: Set location bar to "path" */
228
191
    current_path = path;
229
192
 
230
193
    /* Clear away the existing page contents */
231
194
    clearpage();
 
195
    /* Display the path at the top, for navigation */
 
196
    presentpath(path);
232
197
 
233
198
    /* Check the status, and if not 200, read the error and handle this as an
234
199
     * error. */
241
206
        return;
242
207
    }
243
208
 
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
209
    /* Check if this is a directory listing or file contents */
307
210
    var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
308
 
    if (isdir)
 
211
    if (!editmode && isdir)
309
212
    {
310
 
        setup_for_listing();
311
 
        home_listing(listing, subjects, path);
 
213
        var listing = response.responseText;
 
214
        /* The listing SHOULD be valid JSON text. Parse it into an object. */
 
215
        try
 
216
        {
 
217
            listing = JSON.parse(listing);
 
218
        }
 
219
        catch (e)
 
220
        {
 
221
            handle_error("The server returned an invalid directory listing");
 
222
            return;
 
223
        }
 
224
        handle_dir_listing(path, listing);
312
225
    }
313
226
    else
314
227
    {
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
 
        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 = '';
386
 
        refresh();
 
228
        /* Treat this as an ordinary file. Get the file type. */
 
229
        var content_type = response.getResponseHeader("Content-Type");
 
230
        var handler_type = get_handler_type(content_type);
 
231
        /* If we're in "edit mode", always treat this file as text */
 
232
        would_be_handler_type = handler_type;
 
233
        if (editmode) handler_type = "text";
 
234
        /* handler_type should now be set to either
 
235
         * "text", "image", "audio" or "binary". */
 
236
        switch (handler_type)
 
237
        {
 
238
        case "text":
 
239
            if (isdir)
 
240
            {
 
241
                handle_text(path_join(path, "untitled"), "",
 
242
                    would_be_handler_type);
 
243
            }
 
244
            else
 
245
            {
 
246
                handle_text(path, response.responseText,
 
247
                    would_be_handler_type);
 
248
            }
 
249
            break;
 
250
        case "image":
 
251
            /* TODO: Custom image handler */
 
252
            handle_binary(path, response.responseText);
 
253
            break;
 
254
        case "audio":
 
255
            /* TODO: Custom audio handler */
 
256
            handle_binary(path, response.responseText);
 
257
            break;
 
258
        case "binary":
 
259
            handle_binary(path);
 
260
            break;
 
261
        }
387
262
    }
388
263
}
389
264
 
393
268
 */
394
269
function clearpage()
395
270
{
 
271
    dom_removechildren(document.getElementById("path"));
396
272
    dom_removechildren(document.getElementById("filesbody"));
397
273
}
398
274
 
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
 
 
413
275
/** Deletes all "dynamic" content on the page necessary to navigate from
414
276
 * one directory listing to another (does not clear as much as clearpage
415
277
 * does).
423
285
    dom_removechildren(document.getElementById("sidepanel"));
424
286
}
425
287
 
 
288
/** Sets the mode to either "file browser" or "text editor" mode.
 
289
 * This modifies the window icon, and selected tab.
 
290
 * \param editmode If True, editor mode. Else, file browser mode.
 
291
 */
 
292
function setmode(editmode)
 
293
{
 
294
    /* Find the DOM elements for the file browser and editor tabs */
 
295
    var tabs = document.getElementById("apptabs");
 
296
    var tab_files = null;
 
297
    var tab_edit = null;
 
298
    var a;
 
299
    var href;
 
300
    for (var i=0; i<tabs.childNodes.length; i++)
 
301
    {
 
302
        /* Find the href of the link within */
 
303
        if (!tabs.childNodes[i].getElementsByTagName) continue;
 
304
        a = tabs.childNodes[i].getElementsByTagName("a");
 
305
        if (a.length == 0) continue;
 
306
        href = a[0].getAttribute("href");
 
307
        if (href == null) continue;
 
308
        if (endswith(href, this_app))
 
309
            tab_files = tabs.childNodes[i];
 
310
        else if (endswith(href, edit_app))
 
311
            tab_edit = tabs.childNodes[i];
 
312
    }
 
313
 
 
314
    if (editmode)
 
315
    {
 
316
        tab_files.removeAttribute("class");
 
317
        tab_edit.setAttribute("class", "thisapp");
 
318
    }
 
319
    else
 
320
    {
 
321
        tab_edit.removeAttribute("class");
 
322
        tab_files.setAttribute("class", "thisapp");
 
323
    }
 
324
}
 
325
 
426
326
/*** HANDLERS for different types of responses (such as dir listing, file,
427
327
 * etc). */
428
328
 
429
 
/* handle_error.
430
 
 * message may either be a string, or a DOM node, which will be placed inside
431
 
 * a div.
432
 
 */
433
329
function handle_error(message)
434
330
{
 
331
    setmode(false);
435
332
    var files = document.getElementById("filesbody");
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
 
    }
 
333
    var txt_elem = dom_make_text_elem("div", "Error: "
 
334
        + message.toString() + ".")
448
335
    txt_elem.setAttribute("class", "padding error");
449
336
    files.appendChild(txt_elem);
450
337
}
451
338
 
452
 
/** Given a path, filename and optional revision, returns a URL to open that
453
 
 *  revision of that file.
 
339
/** Presents a path list (address bar inside the page) for clicking.
454
340
 */
455
 
function build_revision_url(path, filename, revision)
 
341
function presentpath(path)
456
342
{
457
 
    bits = {'path': app_path(this_app, path, filename)};
458
 
    if (current_revision)
 
343
    var dom_path = document.getElementById("path");
 
344
    var href_path = make_path(this_app);
 
345
    var nav_path = "";
 
346
    var dir;
 
347
 
 
348
    /* Also set the document title */
 
349
    document.title = path_basename(path) + " - IVLE";
 
350
    /* Create all of the paths */
 
351
    var pathlist = path.split("/");
 
352
    for (var i=0; i<pathlist.length; i++)
459
353
    {
460
 
        bits['query_string'] = 'r=' + revision;
 
354
        dir = pathlist[i];
 
355
        if (dir == "") continue;
 
356
        /* Make an 'a' element */
 
357
        href_path = path_join(href_path, dir);
 
358
        nav_path = path_join(nav_path, dir);
 
359
        var link = dom_make_link_elem("a", dir, "Navigate to " + nav_path,
 
360
                href_path/*, "navigate(" + repr(href_path) + ")"*/);
 
361
        dom_path.appendChild(link);
 
362
        dom_path.appendChild(document.createTextNode("/"));
461
363
    }
462
 
    return build_url(bits);
 
364
    dom_path.removeChild(dom_path.lastChild);
463
365
}
464
366
 
465
367
/** Given a mime type, returns the path to the icon.
511
413
 */
512
414
function handle_binary(path)
513
415
{
 
416
    setmode(false);
514
417
    var files = document.getElementById("filesbody");
515
418
    var div = document.createElement("div");
516
419
    files.appendChild(div);
525
428
    div.appendChild(par2);
526
429
}
527
430
 
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 */
554
 
function update_actions()
555
 
{
556
 
    var file;
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
 
    
571
 
    if (numsel <= 1)
572
 
    {
573
 
        if (numsel == 0)
574
 
        {
575
 
            /* Display information about the current directory instead */
576
 
            filename = path_basename(current_path);
577
 
            file = current_file;
578
 
        }
579
 
        else if (numsel == 1)
580
 
        {
581
 
            filename = selected_files[0];
582
 
            file = file_listing[filename];
583
 
        }
584
 
 
585
 
        /* Update each action node in the topbar.
586
 
         * This includes enabling/disabling actions as appropriate, and
587
 
         * setting href/onclick attributes. */
588
 
    }
589
 
 
590
 
    /* Open */
591
 
    /* Available if exactly one file is selected */
592
 
    var open = document.getElementById("act_open");
593
 
    if (numsel == 1)
594
 
    {
595
 
        open.setAttribute("class", "choice");
596
 
        if (file.isdir)
597
 
            open.setAttribute("title",
598
 
                "Navigate to this directory in the file browser");
599
 
        else
600
 
            open.setAttribute("title",
601
 
                "Edit or view this file");
602
 
        open.setAttribute("href", build_revision_url(current_path, filename,
603
 
                                                     current_revision));
604
 
    }
605
 
    else
606
 
    {
607
 
        open.setAttribute("class", "disabled");
608
 
        open.removeAttribute("title");
609
 
        open.removeAttribute("href");
610
 
    }
611
 
 
612
 
    /* Serve */
613
 
    /* Available if zero or one files are selected,
614
 
     * and only if this is a file, not a directory */
615
 
    var serve = document.getElementById("act_serve");
616
 
    if (numsel <= 1 && !file.isdir && current_file.svnstatus != 'revision')
617
 
    {
618
 
        serve.setAttribute("class", "choice");
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));
627
 
    }
628
 
    else
629
 
    {
630
 
        serve.setAttribute("class", "disabled");
631
 
        serve.removeAttribute("href");
632
 
        serve.removeAttribute("onclick");
633
 
    }
634
 
 
635
 
    /* Run */
636
 
    /* Available if exactly one file is selected,
637
 
     * and it is a Python file.
638
 
     */
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
 
    }
662
 
 
663
 
    /* Download */
664
 
    /* Always available for current files.
665
 
     * If 0 files selected, download the current file or directory as a ZIP.
666
 
     * If 1 directory selected, download it as a ZIP.
667
 
     * If 1 non-directory selected, download it.
668
 
     * If >1 files selected, download them all as a ZIP.
669
 
     */
670
 
    var download = document.getElementById("act_download");
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")
679
 
        if (numsel == 0)
680
 
        {
681
 
            download.setAttribute("href",
682
 
                app_path(download_app, current_path));
683
 
            if (file.isdir)
684
 
                download.setAttribute("title",
685
 
                    "Download the current directory as a ZIP file");
686
 
            else
687
 
                download.setAttribute("title",
688
 
                    "Download the current file");
689
 
        }
690
 
        else
691
 
        {
692
 
            download.setAttribute("href",
693
 
                app_path(download_app, current_path, filename));
694
 
            if (file.isdir)
695
 
                download.setAttribute("title",
696
 
                    "Download the selected directory as a ZIP file");
697
 
            else
698
 
                download.setAttribute("title",
699
 
                    "Download the selected file");
700
 
        }
701
 
    }
702
 
    else
703
 
    {
704
 
        /* Make a query string with all the files to download */
705
 
        var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
706
 
        for (var i=0; i<numsel; i++)
707
 
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
708
 
        dlpath = dlpath.substr(0, dlpath.length-1);
709
 
        download.setAttribute("class", "choice")
710
 
        download.setAttribute("href", dlpath);
711
 
        download.setAttribute("title",
712
 
            "Download the selected files as a ZIP file");
713
 
    }
714
 
 
715
 
    /* Refresh - No changes required */
716
 
 
717
 
    /* Publish and Submit */
718
 
    /* If this directory is under subversion and selected/unselected file is a
719
 
     * directory. */
720
 
    var publish = document.getElementById("act_publish");
721
 
    var submit = document.getElementById("act_submit");
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.firstChild.nodeValue = "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.firstChild.nodeValue = "Publish";
737
 
        }
738
 
    }
739
 
    set_action_state(["publish", "submit"], pubcond);
740
 
 
741
 
    /* Share */
742
 
    /* If exactly 1 non-directory file is selected, and its parent
743
 
     * directory is published.
744
 
     */
745
 
    set_action_state("share", numsel == 1 && !file.isdir &&
746
 
                     current_file.published);
747
 
 
748
 
    /* Rename */
749
 
    /* If exactly 1 file is selected */
750
 
    set_action_state("rename", numsel == 1);
751
 
 
752
 
    /* Delete, cut, copy */
753
 
    /* If >= 1 file is selected */
754
 
    set_action_state(["delete", "cut", "copy"], numsel >= 1);
755
 
 
756
 
    /* Paste, new file, new directory, upload */
757
 
    /* Disable if the current file is not a directory */
758
 
    set_action_state(["paste", "newfile", "mkdir", "upload"], current_file.isdir);
759
 
 
760
 
    /* Subversion actions */
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
 
    single_ivle_versioned_path = (
785
 
         (
786
 
          (numsel == 1 && (stat = file_listing[selected_files[0]])) ||
787
 
          (numsel == 0 && (stat = current_file))
788
 
         ) && stat.svnstatus != "unversioned"
789
 
           && stat.svnurl
790
 
           && stat.svnurl.substr(0, svn_base.length) == svn_base);
791
 
    set_action_state(["submit"], single_ivle_versioned_path);
792
 
 
793
 
    /* There is currently nothing on the More Actions menu of use
794
 
     * when the current file is not a directory. Hence, just remove
795
 
     * it entirely.
796
 
     * (This makes some of the above decisions somewhat redundant).
797
 
     * We also take this opportunity to show the appropriate actions2
798
 
     * bar for this path. It should either be a save or upload widget.
799
 
     */
800
 
    if (current_file.isdir)
801
 
    {
802
 
        var actions2_directory = document.getElementById("actions2_directory");
803
 
        actions2_directory.setAttribute("style", "display: inline;");
804
 
        var moreactions = document.getElementById("moreactions_area");
805
 
        moreactions.setAttribute("style", "display: inline;");
806
 
    }
807
 
    else
808
 
    {
809
 
        var actions2_file = document.getElementById("actions2_file");
810
 
        actions2_file.setAttribute("style", "display: inline;");
811
 
    }
812
 
 
813
 
    return;
814
 
}
815
 
 
816
 
/** Event handler for when an item of the "More actions..." dropdown box is
817
 
 * selected. Performs the selected action. */
818
 
function handle_moreactions()
819
 
{
820
 
    var moreactions = document.getElementById("moreactions");
821
 
    if (moreactions.value == "top")
822
 
        return;
823
 
    var selectedaction = moreactions.value;
824
 
    /* Reset to "More actions..." */
825
 
    moreactions.selectedIndex = 0;
826
 
 
827
 
    /* If 0 files selected, filename is the name of the current dir.
828
 
     * If 1 file selected, filename is that file.
829
 
     */
830
 
    if (selected_files.length == 0)
831
 
        filename = path_basename(current_path);
832
 
    else if (selected_files.length == 1)
833
 
        filename = selected_files[0];
834
 
    else
835
 
        filename = null;
836
 
 
837
 
    /* Now handle the selected action */
838
 
    switch(selectedaction)
839
 
    {
840
 
    case "publish":
841
 
        action_publish(selected_files);
842
 
        break;
843
 
    case "unpublish":
844
 
        action_unpublish(selected_files);
845
 
        break;
846
 
    case "share":
847
 
        window.open(public_app_path("~" + current_path, filename), 'share')
848
 
        break;
849
 
    case "submit":
850
 
        if (selected_files.length == 1)
851
 
            stat = file_listing[selected_files[0]];
852
 
        else
853
 
            stat = current_file;
854
 
        path = stat.svnurl.substr(svn_base.length);
855
 
 
856
 
        /* The working copy might not have an up-to-date version of the
857
 
         * directory. While submitting like this could yield unexpected
858
 
         * results, we should really submit the latest revision to minimise
859
 
         * terrible mistakes - so we run off and ask fileservice for the
860
 
         * latest revision.*/
861
 
        $.post(app_path(service_app, current_path),
862
 
            {"action": "svnrepostat", "path": path},
863
 
            function(result)
864
 
            {
865
 
                window.location = path_join(app_path('+submit'), path) + '?revision=' + result.svnrevision;
866
 
            },
867
 
            "json");
868
 
 
869
 
        break;
870
 
    case "rename":
871
 
        action_rename(filename);
872
 
        break;
873
 
    case "delete":
874
 
        action_delete(selected_files);
875
 
        break;
876
 
    case "copy":
877
 
        action_copy(selected_files);
878
 
        break;
879
 
    case "cut":
880
 
        action_cut(selected_files);
881
 
        break;
882
 
    case "paste":
883
 
        action_paste();
884
 
        break;
885
 
    case "newfile":
886
 
        action_newfile();
887
 
        break;
888
 
    case "mkdir":
889
 
        action_mkdir();
890
 
        break;
891
 
    case "upload":
892
 
        show_uploadpanel(true);
893
 
        break;
894
 
    case "svnadd":
895
 
        action_add(selected_files);
896
 
        break;
897
 
    case "svnremove":
898
 
        action_remove(selected_files);
899
 
        break;
900
 
    case "svnrevert":
901
 
        action_revert(selected_files);
902
 
        break;
903
 
    case "svndiff":
904
 
        window.location = path_join(app_path('diff'), current_path, selected_files[0] || '');
905
 
        break;
906
 
    case "svnupdate":
907
 
        action_update(selected_files);
908
 
        break;
909
 
    case "svnresolved":
910
 
        action_resolved(selected_files);
911
 
        break;
912
 
    case "svncommit":
913
 
        action_commit(selected_files);
914
 
        break;
915
 
    case "svnlog":
916
 
        window.location = path_join(app_path('svnlog'), current_path, selected_files[0] || '');
917
 
        break;
918
 
    case "svncopy":
919
 
        action_svncopy(selected_files);
920
 
        break;
921
 
    case "svncut":
922
 
        action_svncut(selected_files);
923
 
        break;
924
 
    }
925
 
}
926
 
 
927
 
/** User clicks "Run" button.
928
 
 * Do an Ajax call and print the test output.
929
 
 */
930
 
function runfile(localpath)
931
 
{
932
 
    if (!maybe_save('The last saved version will be run.')) return false;
933
 
 
934
 
    /* Dump the entire file to the console */
935
 
    var callback = function()
936
 
    {
937
 
        console_enter_line("execfile('" + localpath + "')", "block");
938
 
    }
939
 
    start_server(callback)
940
 
    return;
941
 
}
942
 
 
943
431
/** Called when the page loads initially.
944
432
 */
945
 
function browser_init()
 
433
window.onload = function()
946
434
{
947
435
    /* Navigate (internally) to the path in the URL bar.
948
436
     * This causes the page to be populated with whatever is at that address,
951
439
    var path = parse_url(window.location.href).path;
952
440
    /* Strip out root_dir + "/files" from the front of the path */
953
441
    var strip = make_path(this_app);
 
442
    var editmode = false;
954
443
    if (path.substr(0, strip.length) == strip)
955
444
        path = path.substr(strip.length+1);
956
445
    else
960
449
        if (path.substr(0, strip.length) == strip)
961
450
        {
962
451
            path = path.substr(strip.length+1);
 
452
            editmode = true;
963
453
        }
964
454
    }
965
455
 
970
460
        path = username;
971
461
    }
972
462
 
973
 
    navigate(path);
 
463
    navigate(path, editmode);
974
464
}