Add bookmarking to Trackster. Also group Trackster options into a single menu button.

This commit is contained in:
Jeremy Goecks
2011-06-10 13:38:24 -04:00
parent a87db45292
commit f1dd385293
7 changed files with 297 additions and 119 deletions
+2 -1
View File
@@ -160,6 +160,7 @@ class UsesVisualization( SharableItemSecurity ):
if visualization.type == 'trackster':
# Trackster config; taken from tracks/browser
latest_revision = visualization.latest_revision
bookmarks = latest_revision.config.get( 'bookmarks', [] )
tracks = []
# Set tracks.
@@ -193,7 +194,7 @@ class UsesVisualization( SharableItemSecurity ):
} )
config = { "title": visualization.title, "vis_id": trans.security.encode_id( visualization.id ),
"tracks": tracks, "chrom": "", "dbkey": visualization.dbkey }
"tracks": tracks, "bookmarks": bookmarks, "chrom": "", "dbkey": visualization.dbkey }
if 'viewport' in latest_revision.config:
config['viewport'] = latest_revision.config['viewport']
+6 -3
View File
@@ -571,6 +571,8 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss
vis_rev.dbkey = dbkey
# Tracks from payload
tracks = []
# TODO: why go through the trouble of unpacking config only to repack and
# put in database? How about sticking JSON directly into database?
for track in decoded_payload['tracks']:
tracks.append( { "dataset_id": track['dataset_id'],
"hda_ldda": track.get('hda_ldda', "hda"),
@@ -579,14 +581,15 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss
"prefs": track['prefs'],
"is_child": track.get('is_child', False)
} )
bookmarks = decoded_payload[ 'bookmarks' ]
vis_rev.config = { "tracks": tracks, "bookmarks": bookmarks }
# Viewport from payload
if 'viewport' in decoded_payload:
chrom = decoded_payload['viewport']['chrom']
start = decoded_payload['viewport']['start']
end = decoded_payload['viewport']['end']
vis_rev.config = { "tracks": tracks, "viewport": { 'chrom': chrom, 'start': start, 'end': end } }
else:
vis_rev.config = { "tracks": tracks }
vis_rev.config[ "viewport" ] = { 'chrom': chrom, 'start': start, 'end': end }
vis.latest_revision = vis_rev
session.add( vis_rev )
session.flush()
@@ -48,3 +48,7 @@ input{font:10px verdana;}
.param-label{float:left;font-weight:bold;padding-top:0.2em;}
.child-track-icon{background:url('../images/fugue/arrow-000-small-bw.png') no-repeat;width:30px;cursor:move;}
.track-resize{background:white url('../images/visualization/draggable_vertical.png') no-repeat top center;position:absolute;right:3px;bottom:-4px;width:14px;height:7px;border:solid #999 1px;z-index:100;}
.bookmark{background:white;border:solid #999 1px;border-right:none;margin:0.5em;margin-right:0;padding:0.5em}
.bookmark .position{font-weight:bold;}
.icon-button.import{margin-left:0.5em;width:100%;}
.delete-icon-container{float:right;}
+18
View File
@@ -278,4 +278,22 @@ input {
border: solid #999 1px;
z-index: 100;
}
.bookmark {
background:white;
border:solid #999 1px;
border-right:none;
margin:0.5em;
margin-right:0;
padding:0.5em
}
.bookmark .position {
font-weight:bold;
}
.icon-button.import {
margin-left:0.5em;
width:100%;
}
.delete-icon-container {
float:right;
}
+72 -3
View File
@@ -330,7 +330,76 @@ function replace_big_select_inputs(min_length, max_length) {
});
}
// Edit and save text asynchronously.
/**
* Returns editable text element. Element is a div with text: (a) when user clicks on text, a textbox/area
* enables user to edit text; (b) when user presses enter key, element's text is set.
*/
// TODO: use this function to implement async_save_text (implemented below).
function get_editable_text_elt(text, use_textarea, num_cols, num_rows, on_finish) {
// Set defaults if necessary.
if (num_cols === undefined) {
num_cols = 30;
}
if (num_rows === undefined) {
num_rows = 4;
}
// Create div for element.
var container = $("<div/>").addClass("editable-text").text(text).click(function() {
// If there's already an input element, editing is active, so do nothing.
if ($(this).children(":input").length > 0) {
return;
}
container.removeClass("editable-text");
// Set element text.
var set_text = function(new_text) {
if (new_text != "") {
container.text(new_text);
}
else {
// Need a line so that there is a click target.
container.html("<br>");
}
container.addClass("editable-text");
};
// Create input element for editing.
var cur_text = container.text(),
input_elt = (use_textarea ?
$("<textarea></textarea>").attr({ rows: num_rows, cols: num_cols }).text( $.trim(cur_text) ) :
$("<input type='text'></input>").attr({ value: $.trim(cur_text), size: num_cols })
).blur(function() {
$(this).remove();
set_text(cur_text);
}).keyup(function(e) {
if (e.keyCode === 27) {
// Escape key.
$(this).trigger("blur");
} else if (e.keyCode === 13) {
// Enter key.
$(this).remove();
var new_text = $(this).val();
set_text(new_text);
if (on_finish) {
on_finish(new_text);
}
}
});
// Replace text with input object and focus & select.
container.text("");
container.append(input_elt);
input_elt.focus();
input_elt.select();
});
return container;
}
/**
* Edit and save text asynchronously.
*/
function async_save_text(click_to_edit_elt, text_elt_id, save_url, text_parm_name, num_cols, use_textarea, num_rows, on_start, on_finish) {
// Set defaults if necessary.
if (num_cols === undefined) {
@@ -341,7 +410,7 @@ function async_save_text(click_to_edit_elt, text_elt_id, save_url, text_parm_nam
}
// Set up input element.
$("#" + click_to_edit_elt).live( "click", function() {
$("#" + click_to_edit_elt).live("click", function() {
// Check if this is already active
if ( $("#renaming-active").length > 0) {
return;
@@ -399,7 +468,7 @@ function async_save_text(click_to_edit_elt, text_elt_id, save_url, text_parm_nam
}
// Replace text with input object and focus & select.
text_elt.hide();
t.insertAfter( text_elt );
t.insertAfter(text_elt);
t.focus();
t.select();
+194 -112
View File
@@ -3,7 +3,7 @@
<%def name="init()">
<%
self.has_left_panel=False
self.has_right_panel=False
self.has_right_panel=True
self.active_view="visualization"
self.message_box_visible=False
%>
@@ -35,19 +35,6 @@
</style>
</%def>
<%def name="center_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<div style="float:left;" id="title"></div>
<a class="panel-header-button right-float" href="${h.url_for( controller='visualization', action='list' )}">Close</a>
<a id="save-button" class="panel-header-button right-float" href="javascript:void(0);">Save</a>
<a id="add-track" class="panel-header-button right-float" href="javascript:void(0);">Add Tracks</a>
</div>
</div>
<div id="browser-container" class="unified-panel-body"></div>
</%def>
<%def name="javascripts()">
${parent.javascripts()}
@@ -72,8 +59,36 @@ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.e
converted_datasets_state_url = "${h.url_for( action='converted_datasets_state' )}",
addable_track_types = { "LineTrack": LineTrack, "FeatureTrack": FeatureTrack, "ReadTrack": ReadTrack },
view;
/**
* Add bookmark.
*/
var add_bookmark = function(position, annotation) {
var
bookmarks_container = $("#bookmarks-container"),
new_bookmark = $("<div/>").addClass("bookmark").appendTo(bookmarks_container),
delete_icon_container = $("<div/>").addClass("delete-icon-container").appendTo(new_bookmark).click(function (){
// Remove bookmark.
new_bookmark.slideUp("fast");
new_bookmark.remove();
view.has_changes = true;
return false;
}),
delete_icon = $("<a href=''/>").addClass("icon-button delete").appendTo(delete_icon_container),
position_div = $("<div/>").addClass("position").text(position).appendTo(new_bookmark),
annotation_div = get_editable_text_elt(annotation, false).addClass("annotation").appendTo(new_bookmark);
view.has_changes = true;
return new_bookmark;
}
$(function() {
// Hide bookmarks by default right now.
parent.force_right_panel("hide");
// Resize view when showing/hiding right panel (bookmarks for now).
$("#right-border").click(function() { view.resize_window(); });
%if config:
var callback;
@@ -107,6 +122,17 @@ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.e
parent_obj.add_track(track);
}
init();
// Load bookmarks.
var bookmarks = JSON.parse('${ h.to_json_string( config.get('bookmarks') ) }'),
bookmark;
for (var i = 0; i < bookmarks.length; i++) {
bookmark = bookmarks[i];
add_bookmark(bookmark['position'], bookmark['annotation']);
}
// View has no changes as of yet.
view.has_changes = false;
%else:
var continue_fn = function() {
view = new View( $("#browser-container"), $("#new-title").val(), undefined, $("#new-dbkey").val() );
@@ -149,13 +175,13 @@ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.e
track_data.prefs, track_data.filters, track_data.tool );
view.add_track(new_track);
// Should replace with live event but can't get working
sortable( new_track.container_div, ".draghandle" );
sortable(new_track.container_div, ".draghandle");
view.has_changes = true;
$("#no-tracks").hide();
};
%if add_dataset is not None:
$.ajax( {
$.ajax({
url: "${h.url_for( action='add_track_async' )}",
data: { hda_id: "${add_dataset}" },
dataType: "json",
@@ -164,105 +190,132 @@ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.e
%endif
// Use a popup grid to add more tracks
$("#add-track").bind("click", function(e) {
$.ajax({
url: "${h.url_for( action='list_histories' )}",
data: { "f-dbkey": view.dbkey },
error: function() { alert( "Grid failed" ); },
success: function(table_html) {
show_modal(
"Select datasets for new tracks",
table_html, {
"Cancel": function() {
hide_modal();
},
"Insert": function() {
var requests = [];
$('input[name=id]:checked,input[name=ldda_ids]:checked').each(function() {
var data,
id = $(this).val();
if ($(this).attr("name") === "id") {
data = { hda_id: id };
} else {
data = { ldda_id: id};
}
requests[requests.length] = $.ajax({
url: "${h.url_for( action='add_track_async' )}",
data: data,
dataType: "json",
});
});
// To preserve order, wait until there are definitions for all tracks and then add
// them sequentially.
$.when.apply($, requests).then(function() {
// jQuery always returns an Array for arguments, so need to look at first element
// to determine whether multiple requests were made and consequently how to
// map arguments to track definitions.
var track_defs = (arguments[0] instanceof Array ?
$.map(arguments, function(arg) { return arg[0]; }) :
[ arguments[0] ]
);
for (var i= 0; i < track_defs.length; i++) {
add_async_success(track_defs[i]);
}
});
hide_modal();
$("#viz-options-button").css( "position", "relative" );
make_popupmenu( $("#viz-options-button"), {
"Add Tracks": function() {
// Use a popup grid to add more tracks
$.ajax({
url: "${h.url_for( action='list_histories' )}",
data: { "f-dbkey": view.dbkey },
error: function() { alert( "Grid failed" ); },
success: function(table_html) {
show_modal(
"Select datasets for new tracks",
table_html, {
"Cancel": function() {
hide_modal();
},
"Insert": function() {
var requests = [];
$('input[name=id]:checked,input[name=ldda_ids]:checked').each(function() {
var data,
id = $(this).val();
if ($(this).attr("name") === "id") {
data = { hda_id: id };
} else {
data = { ldda_id: id};
}
requests[requests.length] = $.ajax({
url: "${h.url_for( action='add_track_async' )}",
data: data,
dataType: "json",
});
});
// To preserve order, wait until there are definitions for all tracks and then add
// them sequentially.
$.when.apply($, requests).then(function() {
// jQuery always returns an Array for arguments, so need to look at first element
// to determine whether multiple requests were made and consequently how to
// map arguments to track definitions.
var track_defs = (arguments[0] instanceof Array ?
$.map(arguments, function(arg) { return arg[0]; }) :
[ arguments[0] ]
);
for (var i= 0; i < track_defs.length; i++) {
add_async_success(track_defs[i]);
}
});
hide_modal();
}
}
}
);
}
});
);
}
});
},
"Save": function() {
// Show saving dialog box
show_modal("Saving...", "<img src='${h.url_for('/static/images/yui/rel_interstitial_loading.gif')}'/>");
// Save all tracks.
var tracks = [];
$(".viewport-container .track").each(function () {
// ID has form track_<main_track_id>_<child_track_id>
var
id_split = $(this).attr("id").split("_"),
track_id = id_split[1],
child_id = id_split[2];
// Get track.
var track = view.tracks[track_id];
if (child_id) {
track = track.child_tracks[child_id];
}
// Add track.
tracks.push( {
"track_type": track.track_type,
"name": track.name,
"hda_ldda": track.hda_ldda,
"dataset_id": track.dataset_id,
"prefs": track.prefs,
"is_child": (child_id ? true : false )
});
});
// Save all bookmarks.
var bookmarks = [];
$(".bookmark").each(function() {
bookmarks[bookmarks.length] = {
position: $(this).children(".position").text(),
annotation: $(this).children(".annotation").text()
};
});
var payload = {
'tracks': tracks,
'viewport': { 'chrom': view.chrom, 'start': view.low , 'end': view.high },
'bookmarks': bookmarks
};
$.ajax({
url: "${h.url_for( action='save' )}",
type: "POST",
data: {
'vis_id': view.vis_id,
'vis_title': view.title,
'dbkey': view.dbkey,
'payload': JSON.stringify(payload)
},
success: function(vis_id) {
view.vis_id = vis_id;
view.has_changes = false;
hide_modal();
},
error: function() { alert("Could not save visualization"); }
});
},
"Bookmarks": function() {
// HACK -- use style to determine if panel is hidden and hide/show accordingly.
parent.force_right_panel(($("div#right").css("right") == "0px" ? "hide" : "show"));
},
"Close": function() { window.location = "${h.url_for( controller='visualization', action='list' )}"; }
});
$("#save-button").bind("click", function(e) {
// Show saving dialog box
show_modal("Saving...", "<img src='${h.url_for('/static/images/yui/rel_interstitial_loading.gif')}'/>");
// Save all tracks.
var tracks = [];
$(".viewport-container .track").each(function () {
// ID has form track_<main_track_id>_<child_track_id>
var
id_split = $(this).attr("id").split("_"),
track_id = id_split[1],
child_id = id_split[2];
// Get track.
var track = view.tracks[track_id];
if (child_id) {
track = track.child_tracks[child_id];
}
// Add track.
tracks.push( {
"track_type": track.track_type,
"name": track.name,
"hda_ldda": track.hda_ldda,
"dataset_id": track.dataset_id,
"prefs": track.prefs,
"is_child": (child_id ? true : false )
});
});
var payload = { 'tracks': tracks, 'viewport': { 'chrom': view.chrom, 'start': view.low , 'end': view.high } };
$.ajax({
url: "${h.url_for( action='save' )}",
type: "POST",
data: {
'vis_id': view.vis_id,
'vis_title': view.title,
'dbkey': view.dbkey,
'payload': JSON.stringify(payload)
},
success: function(vis_id) {
view.vis_id = vis_id;
view.has_changes = false;
hide_modal();
},
error: function() { alert("Could not save visualization"); }
});
$("#add-bookmark-button").click(function() {
// Add new bookmark.
var position = view.chrom + ":" + view.low + "-" + view.high,
annotation = "Bookmark description";
return add_bookmark(position, annotation);
});
//
@@ -298,3 +351,32 @@ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.e
</script>
</%def>
<%def name="center_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<div style="float:left;" id="title"></div>
<div style="float: right">
<a id="viz-options-button" class='panel-header-button popup' href="javascript:void(0)" target="galaxy_main">${_('Options')}</a>
</div>
</div>
</div>
<div id="browser-container" class="unified-panel-body"></div>
</%def>
<%def name="right_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
Bookmarks
</div>
</div>
<div class="unified-panel-body" style="overflow: auto;">
<div id="bookmarks-container"></div>
<div>
<a class="icon-button import" original-title="Add Bookmark" id="add-bookmark-button" href="javascript:void(0);">Add Bookmark</a>
</div>
</div>
</%def>
+1
View File
@@ -78,6 +78,7 @@
if (container_element.parents(".item-content").length > 0) { // Embedded viz
container_element.parents(".item-content").css( { "max-height": "none", "overflow": "visible" } );
} else { // Viewing just one shared viz
// TODO: need live or just bind click?
$("#right-border").live("click", function() { view.resize_window(); });
}