2 hanndles clip edit controls
3 'inoutpoints':0, //should let you set the in and out points of clip
4 'panzoom':0, //should allow setting keyframes and tweenning modes
5 'overlays':0, //should allow setting "locked to clip" overlay tracks
6 'audio':0 //should allow controlling the audio volume (with keyframes)
10 "mwe-crop" : "Crop image",
11 "mwe-apply_crop" : "Apply crop to image",
12 "mwe-reset_crop" : "Reset crop",
13 "mwe-insert_image_page" : "Insert into page",
14 "mwe-insert_into_sequence" : "Insert into sequence",
15 "mwe-preview_insert" : "Preview insert",
16 "mwe-cancel_image_insert" : "Cancel insert",
17 "mwe-sc_fileopts" : "Clip detail edit",
18 "mwe-sc_inoutpoints" : "Set in-out points",
19 "mwe-sc_overlays" : "Overlays",
20 "mwe-sc_audio" : "Audio control",
21 "mwe-sc_duration" : "Duration",
22 "mwe-template_properties" : "Template properties",
23 "mwe-custom_title" : "Custom title",
24 "mwe-edit_properties" : "Edit properties",
25 "mwe-other_properties" : "Other properties",
26 "mwe-resource_page" : "Resource page:",
27 "mwe-set_in_out_points" : "Set in-out points",
28 "mwe-start_time" : "Start time",
29 "mwe-end_time" : "End time",
30 "mwe-preview_inout" : "Preview\/play in-out points"
33 var default_clipedit_values = {
34 'rObj': null, // the resource object
35 'clip_disp_ct':null,//target clip disp
36 'control_ct':null, //control container
37 'media_type': null, //media type
38 'parent_ct': null, //parent container
40 'p_rsdObj': null, //parent remote search object
41 'p_seqObj': null, //parent sequence Object
43 'controlActionsCb' : null, //the object that configures control Action callbacks
45 'edit_action': null, //the requested edit action
46 'profile': 'inpage' //the given profile either "inpage" or "sequence"
48 var mvClipEdit = function(iObj) {
49 return this.init(iObj);
51 mvClipEdit.prototype = {
53 selTool:null, //selected tool
54 crop: null, //the crop values
59 for(var i in default_clipedit_values){
65 //if media type was not supplied detect for resource if possible:
66 //@@todo more advanced detection.
67 if(!this.media_type && this.rObj && this.rObj.type ){
68 if( this.rObj.type.indexOf("image/") === 0){
69 this.media_type = 'image';
70 }else if( this.rObj.type.indexOf("video/") === 0){
71 this.media_type = 'video';
72 }else if( this.rObj.type.indexOf("text/") === 0){
73 this.media_type = 'template';
78 if(this.profile == 'sequence'){
79 this.doEditTypesMenu();
82 //check the media_type:
83 //js_log('mvClipEdit:: media type:' + this.media_type + ' base width: ' + this.rObj.width + ' bh: ' + this.rObj.height);
84 //could seperate out into media Types objects for now just call method
85 if(this.media_type == 'image'){
86 this.setUpImageCtrl();
87 }else if(this.media_type=='video'){
88 this.setUpVideoCtrl();
93 //master edit types object:
94 //maybe we should refactor these into their own classes
95 //more refactor each media type should be its own class inheriting the shared baseEditType object
98 'media':['image','template'],
99 'doEdit':function( _this, target ){
100 //(_this is a smilClip instance)
101 //do clock mouse scroll duration editor
103 '<label for="ce_dur">Duration: </label>' +
104 '<input name="ce_dur" tabindex="1" maxlength="11" value="'+
105 seconds2npt( _this.rObj.getDuration() )+
108 ).children("input[name='ce_dur']").change(function(){
109 js_log("update duration:" + $j(this).val() );
110 //update the parent sequence object:
111 _this.rObj.dur = smilParseTime( $j(this).val() );
112 //update the playlist:
113 _this.p_seqObj.do_refresh_timeline( true );
120 'doEdit':function( _this, target ){
121 //do clock mouse scroll duration editor
122 var end_ntp = ( _this.rObj.embed.end_ntp) ? _this.rObj.embed.end_ntp : _this.rObj.embed.getDuration();
124 end_ntp = seconds2npt( _this.rObj.dur );
126 var start_ntp = (_this.rObj.embed.start_ntp) ? _this.rObj.embed.start_ntp : seconds2npt( 0 );
131 _this.getSetInOutHtml({
132 'start_ntp' : start_ntp,
136 _this.setInOutBindings();
140 'media':['image','video','template'],
141 'doEdit':function(_this, target ){
142 //if media type is template we have to query to get its URI to get its paramaters
143 if(_this.media_type == 'template' && !_this.rObj.tVars){
144 mv_set_loading('#sub_cliplib_ic');
145 var reqObj ={ 'action':'query',
147 'titles': _this.rObj.uri,
150 //get the interface uri from the plObject
151 var api_url = _this.p_seqObj.plObj.interface_url;
157 if(typeof data.query.pages == 'undefined')
158 return _this.doEditOpts(target);
159 for(var i in data.query.pages){
160 var page = data.query.pages[i];
161 if(!page['revisions'] || !page['revisions'][0]['*']){
162 return _this.doEditOpts(target);
164 var template_rev = page['revisions'][0]['*'];
168 //do a regular ex to get the ~likely~ template values
169 //(of course this sucks)
170 //but maybe this will make its way into the api sometime soon to support wysiwyg type editors
171 //idealy it would expose a good deal of info about the template params
172 js_log('matching against: ' + template_rev);
173 var tempVars = template_rev.match(/\{\{\{([^\}]*)\}\}\}/gi);
175 _this.rObj.tVars = new Array();
176 for(var i=0; i < tempVars.length; i++){
177 var tvar = tempVars[i].replace('{{{','').replace('}}}','');
178 //strip anything after a |
179 if(tvar.indexOf('|') != -1){
180 tvar = tvar.substr(0, tvar.indexOf('|'));
182 //check for duplicates:
184 for(var j=0; j < _this.rObj.tVars.length; j++){
185 js_log('checking: ' + _this.rObj.tVars[j] + ' against:' + tvar);
186 if( _this.rObj.tVars[j] == tvar)
189 //add the template vars to the output obj
191 _this.rObj.tVars.push( tvar );
193 _this.doEditOpts(target);
197 _this.doEditOpts(target);
202 'media':['image','video'],
203 'doEdit':function(_this, target){
204 //do clock mouse scroll duration editor
205 $j(target).html('<h3>Current Overlays:</h3>Add,Remove,Modify');
209 'media':['image','video', 'template'],
210 'doEdit':function(_this, target){
211 //do clock mouse scroll duration editor
212 $j(target).html('<h3>Audio Volume:</h3>');
216 doEditOpts:function(target){
218 //add html for rObj resource:
221 '<td colspan="2"><b>'+gM('mwe-edit_properties')+'</b></td>'+
225 gM('mwe-custom_title') +
227 '<td><input type="text" size="15" maxwidth="255" value="';
228 if(_this.rObj.title != null)
233 if( _this.rObj.tVars){
234 var existing_p = _this.rObj.params;
235 var testing_a = _this.rObj.tVars;
238 '<td colspan="2"><b>' + gM('mwe-template_properties') + '</b></td>'+
240 for(var i =0; i < _this.rObj.tVars.length ; i++){
243 _this.rObj.tVars[i] +
245 '<td><input name="'+_this.rObj.tVars[i]+'" class="ic_tparam" type="text" size="15" maxwidth="255" value="';
246 if(_this.rObj.params[ _this.rObj.tVars[i] ]){
247 o+= _this.rObj.params[ _this.rObj.tVars[i] ];
254 if(typeof wgArticlePath != 'undefined' ){
255 var res_src = wgArticlePath.replace(/\$1/, _this.rObj.uri );
256 var res_title = _this.rObj.uri;
259 var res_src = _this.rObj.src;
260 var res_title = parseUri(_this.rObj.src).file;
263 '<td colspan="2"><b>'+gM('mwe-other_properties')+'</b></td>'+
267 gM('mwe-resource_page') +
269 '<td><a href="' + res_src +'" '+
276 $j(target).html ( o );
278 //add update bindings
279 $j(target + ' .ic_tparam').change(function(){
280 js_log("updated tparam::" + $j(this).attr("name"));
281 //update param value:
282 _this.rObj.params[ $j(this).attr("name") ] = $j(this).val();
283 //re-parse & update template
284 var template_wiki_text = '{{' + _this.rObj.uri;
285 for(var i =0;i < _this.rObj.tVars.length ; i++){
287 template_wiki_text += "\n|"+_this.rObj.tVars[i] + ' = ' + _this.rObj.params[ _this.rObj.tVars[i] ] ;
289 template_wiki_text += "\n}}";
292 'title' : _this.p_seqObj.plObj.mTitle,
293 'text' : template_wiki_text
295 $j( _this.rObj.embed ).html( mv_get_loading_img() );
297 var api_url = _this.p_seqObj.plObj.interface_url;
302 if(data.parse.text['*']){
304 $j( _this.rObj.embed ).html( data.parse.text['*'] );
309 //update doFocusBindings
311 _this.p_seqObj.doFocusBindings();
313 doEditTypesMenu:function(){
315 //add in subMenus if set
316 //check for submenu and add to item container
319 o+= '<div id="mv_submenu_clipedit">';
321 var first_tab = false;
322 $j.each(this.edit_types, function(sInx, editType){
323 //check if the given editType is valid for our given media type
325 for(var i =0; i < editType.media.length;i++){
326 if( editType.media[i] == _this.media_type){
334 '<a id="mv_smi_'+sInx+'" href="#sc_' + sInx + '">' + gM('mwe-sc_' + sInx ) + '</a>'+
336 tabc += '<div id="sc_' + sInx + '" style="overflow:auto;" ></div>';
341 //add sub menu container with menu html:
342 $j('#'+this.control_ct).html( o ) ;
344 $j('#mv_submenu_clipedit').tabs({
346 select: function(event, ui) {
347 _this.doDisplayEdit( $j(ui.tab).attr('id').replace('mv_smi_', '') );
349 }).addClass('ui-tabs-vertical ui-helper-clearfix');
351 $j("#mv_submenu_clipedit li").removeClass('ui-corner-top').addClass('ui-corner-left');
352 //update the default edit display:
353 _this.doDisplayEdit( first_tab );
355 doDisplayEdit:function( edit_type ){
358 js_log('doDisplayEdit: ' + edit_type );
360 //do edit interface for that edit type:
361 if( this.edit_types[ edit_type ].doEdit )
362 this.edit_types[ edit_type ].doEdit(this, '#sc_'+edit_type );
364 setUpVideoCtrl:function(){
365 js_log('setUpVideoCtrl:f');
367 var eb = $j('#embed_vid').get(0);
368 //turn on preview to avoid onDone actions
369 eb.preview_mode = true;
370 $j('#'+this.control_ct).html('<h3>Edit Video Tools:</h3>');
371 if( eb.supportsURLTimeEncoding() ){
372 $j('#'+this.control_ct).append(
373 _this.getSetInOutHtml({
374 'start_ntp' : eb.start_ntp,
375 'end_ntp' : eb.end_ntp
378 _this.setInOutBindings();
380 //if in a sequence we have no need for insertDesc
381 if( !_this.p_seqObj){
382 $j('#'+this.control_ct).append( _this.getInsertDescHtml() );
384 //update control actions
385 this.updateInsertControlActions();
387 setInOutBindings:function(){
390 var start_sec = npt2seconds($j('#'+this.control_ct + ' .startInOut').val() );
391 var end_sec = npt2seconds($j('#'+this.control_ct + ' .endInOut').val() );
393 //if we don't have 0 as start then assume we are in a range request and give some buffer area:
394 var min_slider = (start_sec - 60 < 0 ) ? 0 : start_sec - 60;
396 var max_slider = end_sec+60;
398 max_slider = end_sec;
401 $j('#'+this.control_ct + ' .inOutSlider').slider({
405 values: [start_sec, end_sec],
406 slide: function(event, ui) {
407 //js_log(" vals:"+ seconds2npt( ui.values[0] ) + ' : ' + seconds2npt( ui.values[1]) );
408 $j('#'+_this.control_ct + ' .startInOut').val( seconds2npt( ui.values[0] ) );
409 $j('#'+_this.control_ct + ' .endInOut').val( seconds2npt( ui.values[1] ) );
411 change:function(event, ui){
412 do_video_time_update( seconds2npt( ui.values[0]), seconds2npt( ui.values[1] ) );
417 $j('#'+this.control_ct + ' .inOutPreviewClip').hover(
419 $j(this).addClass('ui-state-hover');
422 $j(this).removeClass('ui-state-hover');
425 $j('#embed_vid').get(0).stop();
426 $j('#embed_vid').get(0).play();
431 getSetInOutHtml:function( setInt ){
432 return '<strong>' + gM('mwe-set_in_out_points') + '</strong>'+
433 '<table border="0" style="background: transparent; width:94%;height:50px;">'+
435 '<td style="width:55px">'+
436 gM('mwe-start_time') +
437 '<input class="ui-widget-content ui-corner-all startInOut" size="9" value="' + setInt.start_ntp +'">'+
440 '<div class="inOutSlider"></div>'+
442 '<td style="width:55px">'+
444 '<input class="ui-widget-content ui-corner-all endInOut" size="9" value="'+ setInt.end_ntp +'">'+
448 '<a href="#" class="ui-state-default ui-corner-all ui-icon_link inOutPreviewClip"><span class="ui-icon ui-icon-video"></span>'+ gM('mwe-preview_inout') +'</a>';
450 getInsertDescHtml:function(){
451 var o= '<h3>Inline Description</h3>'+
452 '<textarea style="width:95%" id="mv_inline_img_desc" rows="5" cols="30">';
454 //if we have a parent remote search driver let it parse the inline description
455 o+= this.rObj.pSobj.getInlineDescWiki( this.rObj );
457 o+='</textarea><br>';
458 //js_log('getInsertDescHtml: ' + o );
461 updateInsertControlActions:function(){
463 var b_target = _this.p_rsdObj.target_container + '~ .ui-dialog-buttonpane';
464 //empty the ui-dialog-buttonpane bar:
465 $j(b_target).empty();
466 for( var cbType in _this.controlActionsCb ){
469 $j(b_target).append( $j.btnHtml(gM('mwe-insert_into_sequence'), 'mv_insert_sequence', 'check' ) + ' ' )
470 .children('.mv_insert_sequence')
474 _this.controlActionsCb['insert_seq']( _this.rObj );
478 $j(b_target).append( $j.btnHtml(gM('mwe-insert_image_page'), 'mv_insert_image_page', 'check' ) + ' ' )
479 .children('.mv_insert_image_page')
483 _this.controlActionsCb['insert']( _this.rObj );
487 $j(b_target).append( $j.btnHtml( gM('mwe-preview_insert'), 'mv_preview_insert', 'refresh') + ' ' )
488 .children('.mv_preview_insert')
492 _this.controlActionsCb['preview']( _this.rObj );
496 $j(b_target).append( $j.btnHtml( gM('mwe-cancel_image_insert'), 'mv_cancel_img_edit', 'close') + ' ')
497 .children('.mv_cancel_img_edit')
501 _this.controlActionsCb['cancel']( _this.rObj );
507 applyEdit:function(){
509 js_log('applyEdit::' + this.media_type);
510 if(this.media_type == 'image'){
512 }else if(this.media_type == 'video'){
513 this.applyVideoAdj();
515 //copy over the desc text to the resource object
516 _this.rObj['inlineDesc']= $j('#mv_inline_img_desc').val();
518 setUpImageCtrl:function(){
520 //by default apply Crop tool
521 $j('#'+this.control_ct).html(
522 '<h3>Edit tools</h3>' +
523 '<div class="mv_edit_button mv_crop_button_base" id="mv_crop_button" alt="crop" title="'+gM('mwe-crop')+'"/>'+
524 '<a href="#" class="mv_crop_msg">' + gM('mwe-crop') + '</a> '+
525 '<span style="display:none" class="mv_crop_msg_load">' + gM('mwe-loading_txt') + '</span> '+
526 '<a href="#" style="display:none" class="mv_apply_crop">' + gM('mwe-apply_crop') + '</a> '+
527 '<a href="#" style="display:none" class="mv_reset_crop">' + gM('mwe-reset_crop') + '</a> '+
528 '<hr style="clear:both"/><br>'+
529 '<span style="float:left;">Layout:</span>' +
530 '<input type="radio" name="mv_layout" id="mv_layout_left" style="float:left"><div id="mv_layout_left_img" title="'+gM('mwe-layout_left')+'"/>'+
531 '<input type="radio" name="mv_layout" id="mv_layout_right" style="float:left"><div id="mv_layout_right_img" title="'+gM('mwe-layout_left')+'"/>'+
532 '<hr style="clear:both" /><br>' +
533 _this.getInsertDescHtml()
535 //add the actions to the 'button bar'
536 _this.updateInsertControlActions()
539 '<div class="mv_edit_button mv_scale_button_base" id="mv_scale_button" alt="crop" title="'+gM('mwe-scale')+'"></div>'+
540 '<a href="#" class="mv_scale_msg">' + gM('mwe-scale') + '</a><br>'+
541 '<a href="#" style="display:none" class="mv_apply_scale">' + gM('mwe-apply_scale') + '</a> '+
542 '<a href="#" style="display:none" class="mv_reset_scale">' + gM('mwe-reset_scale') + '</a><br> '+
547 //make sure the default is reflected:
548 if( ! _this.rObj.layout )
549 _this.rObj.layout = 'right';
550 $j('#mv_layout_' + _this.rObj.layout)[0].checked = true;
553 $j('#mv_layout_left,#mv_layout_left_img').click(function(){
554 $j('#mv_layout_right')[0].checked = false;
555 $j('#mv_layout_left')[0].checked = true;
556 _this.rObj.layout = 'left';
559 $j('#mv_layout_right,#mv_layout_right_img').click(function(){
560 $j('#mv_layout_left')[0].checked = false;
561 $j('#mv_layout_right')[0].checked = true;
562 _this.rObj.layout = 'right';
564 $j('#mv_crop_button,.mv_crop_msg,.mv_apply_crop').click(function(){
565 js_log('click:mv_crop_button: base width: ' + _this.rObj.width + ' bh: ' + _this.rObj.height);
566 if($j('#mv_crop_button').hasClass('mv_crop_button_selected')){
569 js_log('click:turn on');
573 $j('.mv_reset_crop').click(function(){
574 $j('.mv_apply_crop,.mv_reset_crop').hide();
575 $j('.mv_crop_msg').show();
576 $j('#mv_crop_button').removeClass('mv_crop_button_selected').addClass('mv_crop_button_base').attr('title',gM('mwe-crop'));
577 _this.rObj.crop=null;
578 $j('#' + _this.clip_disp_ct ).empty().html(
579 '<img src="' + _this.rObj.edit_url + '" id="rsd_edit_img">'
583 applyVideoAdj:function(){
584 js_log('applyVideoAdj::');
586 //be sure to "stop the video (some plugins can't have DOM elements on top of them)
587 $j('#embed_vid').get(0).stop();
589 //update video related keys:
591 this.rObj['start_time'] = $j('#'+this.control_ct + ' .startInOut').val();
592 this.rObj['end_time'] = $j('#'+this.control_ct + ' .endInOut').val() ;
594 //do the local video adjust
595 if(typeof this.rObj.pSobj['applyVideoAdj'] != 'undefined'){
596 this.rObj.pSobj.applyVideoAdj( this.rObj );
599 applyCrop:function(){
601 $j('.mv_apply_crop').hide();
602 $j('.mv_crop_msg').show();
603 $j('#mv_crop_button').removeClass('mv_crop_button_selected').addClass('mv_crop_button_base').attr('title',gM('mwe-crop'));
604 js_log( 'click:turn off' );
605 var cat = _this.rObj;
607 //empty out and display croped:
608 $j('#'+_this.clip_disp_ct ).empty().html(
609 '<div id="mv_cropcotainer" style="overflow:hidden;position:absolute;'+
610 'width:' + _this.rObj.crop.w + 'px;'+
611 'height:' + _this.rObj.crop.h + 'px;">'+
612 '<div id="mv_crop_img" style="position:absolute;'+
613 'top:-' + _this.rObj.crop.y +'px;'+
614 'left:-' + _this.rObj.crop.x + 'px;">'+
615 '<img src="' + _this.rObj.edit_url + '">'+
622 //right now enableCrop loads "just in time"
623 //@@todo we really need an "auto loader" type system.
624 enableCrop:function(){
626 $j('.mv_crop_msg').hide();
627 $j('.mv_crop_msg_load').show();
628 var doEnableCrop = function(){
629 $j('.mv_crop_msg_load').hide();
630 $j('.mv_reset_crop,.mv_apply_crop').show();
631 $j('#mv_crop_button').removeClass('mv_crop_button_base').addClass('mv_crop_button_selected').attr('title',gM('mwe-crop_done'));
632 $j('#' + _this.clip_disp_ct + ' img').Jcrop({
633 onSelect: function(c){
634 js_log('on select:' + c.x +','+ c.y+','+ c.x2+','+ c.y2+','+ c.w+','+ c.h);
637 onChange: function(c){
640 //temporary hack (@@todo need to debug why rsd_res_item gets moved )
641 $j('#clip_edit_disp .rsd_res_item').css({
646 //load the jcrop library if needed:
655 // mv_lock_vid_updates defined in mv_stream.js (we need to do some more refactoring )
656 if(typeof mv_lock_vid_updates == 'undefined')
657 mv_lock_vid_updates= false;
659 function add_adjust_hooks(mvd_id, adj_callback){
660 /*myClipEdit = new mvClipEdit({
661 'control_ct': '#mvd_form_'+mvd_id
663 $j('#mvd_form_'+mvd_id).html(
664 mvClipEdit.getSetInOutHtml({
665 'start_ntp' : $j('#mv_start_hr_' + mvd_id).val(),
666 'end_ntp' : $j('#mv_end_hr_' + mvd_id).val()
669 mvClipEdit.setInOutBindings();*/
671 var start_sec = npt2seconds($j('#mv_start_hr_' + mvd_id).val() );
672 var end_sec = npt2seconds($j('#mv_end_hr_' + mvd_id).val() );
674 //if we don't have 0 as start then assume we are in a range request and give some buffer area:
675 var min_slider = (start_sec - 60 < 0 ) ? 0 : start_sec - 60;
677 var max_slider = end_sec+60;
679 max_slider = end_sec;
681 //pre-destroy just in case:
682 $j('#mvd_form_' + mvd_id + ' .inOutSlider').slider( 'destroy' ).slider({
686 values: [start_sec, end_sec],
687 slide: function(event, ui) {
688 js_log(" vals:"+ seconds2npt( ui.values[0] ) + ' : ' + seconds2npt( ui.values[1]) );
689 $j('#mv_start_hr_' + mvd_id).val( seconds2npt( ui.values[0] ) );
690 $j('#mv_end_hr_' + mvd_id).val( seconds2npt( ui.values[1] ) );
692 change:function(event, ui){
693 do_video_time_update( seconds2npt( ui.values[0]), seconds2npt( ui.values[1] ) );
696 $j('.mv_adj_hr').change(function(){
697 //preserve track duration for nav and seq:
698 //ie seems to crash so no interface updates for IE for the time being
699 if(!$j.browser.msie){
700 if(mvd_id=='nav'||mvd_id=='seq'){
701 add_adjust_hooks(mvd_id); // (no adj_callback)
703 add_adjust_hooks(mvd_id)
706 //update the video time for onChange
707 do_video_time_update( $j('#mv_start_hr_'+mvd_id).val(), $j('#mv_end_hr_'+mvd_id).val() );
711 function do_video_time_update(start_time, end_time, mvd_id) {
712 js_log('do_video_time_update: ' +start_time +' '+ end_time);
714 if(mv_lock_vid_updates==false){
715 //update the vid title:
716 $j('#mv_videoPlayerTime').html( start_time + ' to ' + end_time );
717 var ebvid = $j('#embed_vid').get(0);
721 ebvid.updateVideoTime(start_time, end_time);
722 js_log('update thumb: '+ start_time);
723 ebvid.updateThumbTimeNTP( start_time );