Ticket #9939: meta-web5.diff

File meta-web5.diff, 36.8 KB (added by Doug Haber <doug@…>, 9 years ago)

A little more cleanup of how the home page links are created/updated

  • new file js/dialog/dialog.css

    diff --git a/js/dialog/dialog.css b/js/dialog/dialog.css
    new file mode 100644
    index 0000000..7b73405
    - +  
     1/**
     2 * Dialog
     3 *
     4 * @author      Roland Franssen <franssen.roland@gmail.com>
     5 * @license     MIT
     6 * @version     2.1
     7 **/
     8 
     9/* DONT CHANGE */
     10* html .fixed { position:absolute }
     11.fixed { position:fixed }
     12
     13/* DIALOG CORE */
     14#dialog-overlay { top:0;left:0;width:100%;height:100%;z-index:900 }
     15#dialog-container { overflow:hidden;z-index:901;text-align:left }
     16
     17/* DIALOG TOP */
     18#dialog-top{background:#999;border:1px solid #fff;padding:5px;font-weight:bold}
     19#dialog-title{color:#333}
     20#dialog-close{color:#fff;padding-left:5px}
     21#dialog-close:hover{color:#ccc}
     22
     23/* DIALOG BOTTOM */
     24#dialog-bottom{background:#eee;border-top:1px solid #ccc;color:#666;padding:5px;text-align:center;font-size:12px}
     25#dialog-bottom .next,
     26#dialog-bottom .prev{color:#ccc;font-weight:bold;color:#333}
     27#dialog-bottom .next:hover,
     28#dialog-bottom .prev:hover{color:#f90}
     29#dialog-bottom .next{padding-left:10px}
     30#dialog-bottom .prev{padding-right:10px}
     31#dialog-bottom .curr{}
     32
     33/* DIALOG MISC */
     34#dialog-loading{color:#ccc;font-weight:bold;text-align:center;padding:20px}
     35
     36/* DIALOG PREDEFINED */
     37#dialog-container .alert,
     38#dialog-container .confirm { text-align:center;color:#999 }
     39#dialog-container .alert input,
     40#dialog-container .confirm input { font-weight:bold;width:75px }
     41
     42/* DIALOG PERSONAL */
     43#dialog-container .myFirstDialog { color:orange;font-size:20px }
  • new file js/dialog/dialog.js

    diff --git a/js/dialog/dialog.js b/js/dialog/dialog.js
    new file mode 100644
    index 0000000..3b227ff
    - +  
     1/**
     2 * Dialog
     3 *
     4 * @author      Roland Franssen <franssen.roland@gmail.com>
     5 * @license     MIT
     6 * @version     2.1
     7 **/
     8
     9var Dialogs = {
     10        Lang:{
     11                close:   '&nbsp;&times;&nbsp;',
     12                prev:    '&laquo; Previous',
     13                next:    'Next &raquo;',
     14                loading: 'Loading...',
     15                ok:      'OK',
     16                yes:     'Yes',
     17                no:      'No'
     18        },
     19        Default:{
     20                handle:         null,                    // css rule | element | null
     21                autoOpen:       false,                   // true | false
     22                background:     ['#000', '#fff'],        // array
     23                width:          'auto',                  // auto | max | integer
     24                height:         'auto',                  // auto | max | integer
     25                minWidth:       null,                    // null | pixel value
     26                minHeight:      null,                    // null | pixel value
     27                innerScroll:    true,                    // true | false
     28                opacity:        .75,                     // float | false
     29                margin:         10,                      // integer
     30                padding:        10,                      // integer
     31                title:          null,                    // string | null
     32                className:      null,                    // string | null
     33                content:        null,                    // string | element | array | object | function
     34                iframe:         null,                    // string | null
     35                target:{
     36                  id:           null,                    // string | null
     37                  auto:         true                     // true | false
     38                },
     39                ajax:{
     40                  url:          null,                    // string | null
     41                  jsonTemplate: null,                    // interpolation template string | null
     42                  options:      {}                       // default ajax options
     43                },
     44                close:{
     45                  link:         true,                    // true | false
     46                  esc:          true,                    // true | false
     47                  overlay:      true                     // true | false
     48                },
     49                afterOpen:      Prototype.emptyFunction, // function
     50                afterClose:     Prototype.emptyFunction, // function
     51                afterClick:     Prototype.emptyFunction, // function
     52                afterIframeLoad:Prototype.emptyFunction  // function
     53        },
     54        Browser:{
     55                IE6:(Prototype.Browser.IE && parseInt(navigator.appVersion) == 4 && navigator.userAgent.toLowerCase().indexOf('msie 6.') != -1)
     56        }
     57};
     58
     59Object.extend(Dialogs, {
     60        _exec:false,
     61        _open:false,
     62        _elements:{
     63                overlay:['div', 'dialog-overlay', 'fixed'],
     64                container:['div', 'dialog-container', 'fixed'],
     65                content:['div', 'dialog-content'],
     66                loading:['div', 'dialog-loading'],
     67                top:['div', 'dialog-top'],
     68                bottom:['div', 'dialog-bottom'],
     69                title:['span', 'dialog-title'],
     70                close:['a', 'dialog-close'],
     71                next:['a', null, 'next'],
     72                prev:['a', null, 'prev'],
     73                curr:['span', null, 'curr']
     74        },
     75        fix:{
     76                scroll:Dialogs.Browser.IE6,
     77                select:Dialogs.Browser.IE6
     78        },
     79        view:function(){
     80                var view = document.viewport,
     81                    dim  = view.getDimensions(),
     82                        data = {width:dim.width, height:dim.height};
     83                if(Dialogs.fix.scroll){
     84                        var scroll = view.getScrollOffsets();
     85                        data.top  = scroll.top;
     86                        data.left = scroll.left;
     87                }
     88                return data;
     89        },
     90        elm:function(elm){
     91                return Dialogs._elements[elm];
     92        },
     93        load:function(domready){
     94                if(!!Dialogs._exec) return;
     95                Dialogs._exec = true;
     96                var e = Dialogs._elements;
     97                for(var x in e){
     98                        var d = e[x],
     99                            a = {style:'display:none'};
     100                        if(d[1]) a['id'] = d[1];
     101                        if(d[2]) a['className'] = d[2];
     102                        switch(d[0]){
     103                                case 'a': a['href'] = 'javascript:;'; break;
     104                        }
     105                        var el = new Element(d[0], a);
     106                        if(Dialogs.Lang[x]) el.update(Dialogs.Lang[x]);
     107                        Dialogs._elements[x] = el;
     108                }
     109                var load = function(){
     110                        var e = Dialogs._elements;
     111                        $(document.body)
     112                        .insert(e['overlay'])
     113                        .insert(e['container']
     114                                .insert(e['top']
     115                                        .insert(e['title'])
     116                                        .insert(e['close'])
     117                                )
     118                                .insert(e['content'])
     119                                .insert(e['bottom']
     120                                        .insert(e['prev'])
     121                                        .insert(e['curr'])
     122                                        .insert(e['next'])
     123                                )
     124                        );
     125                        if(Dialogs.Browser.IE6) e['top'].insert(new Element('div', {style:'clear:both'}));
     126                };
     127                if(!!domready) document.observe('dom:loaded', load);
     128                else load.call();
     129        },
     130        close:function(){
     131                [Dialogs.elm('title'), Dialogs.elm('content'), Dialogs.elm('curr')].invoke('update', '');
     132                for(var x in Dialogs._elements) Dialogs._elements[x].writeAttribute('style', 'display:none');
     133                Dialogs.elm('container').setStyle('left:-99999px;top:-99999px');
     134                if(Dialogs.fix.select)
     135                        $$('select.dialog-hideselect').invoke('show').invoke('removeClassName', 'dialog-hideselect');
     136                Dialogs._open = false;
     137        },
     138        alert:function(s){
     139                var o = new Element('input', {value:Dialogs.Lang.ok, type:'button'}),
     140                    a = new Dialog({
     141                                className:'alert',
     142                                close:{link:false, esc:true},
     143                                padding:20,
     144                                content:function(){
     145                                        o.observe('click', Dialogs.close);
     146                                        return [s, '<br /><br />', o];
     147                                },
     148                                afterOpen:function(){
     149                                        o.focus();
     150                                }
     151                        });
     152                a.open();
     153        },
     154        confirm:function(s, y_call, n_call){
     155                var y = new Element('input', {value:Dialogs.Lang.yes, type:'button'}),
     156                    n = new Element('input', {value:Dialogs.Lang.no, type:'button'}),
     157                    c = new Dialog({
     158                                className:'confirm',
     159                                close:{link:false},
     160                                padding:20,
     161                                content:function(){
     162                                        y.observe('click', function(){
     163                                                if(Object.isFunction(y_call)) y_call();
     164                                                else Dialogs.close();
     165                                        });
     166                                        n.observe('click', function(){
     167                                                if(Object.isFunction(n_call)) n_call();
     168                                                else Dialogs.close();
     169                                        });
     170                                        return [s, '<br /><br />', y, n];
     171                                },
     172                                afterOpen:function(){
     173                                        y.focus();
     174                                }
     175                        });
     176                c.open();
     177        }
     178});
     179var Dialog = Class.create();
     180Dialog.prototype = {
     181        initialize:function(opt){
     182                this.opt = Object.extend(Object.clone(Dialogs.Default), opt || {});
     183                var c = this.opt.content;
     184                if(Object.isFunction(c))
     185                        Object.extend(this.opt, {content:c()});
     186                c = this.opt.content;
     187                if(Object.isString(this.opt.target.id) || Object.isElement(this.opt.target.id)){
     188                        var b = $(this.opt.target.id);
     189                        Object.extend(this.opt, {content:b.innerHTML});
     190                        if(this.opt.target.auto){
     191                                var a = /#(.+)$/.exec(window.location);
     192                                if(Object.isArray(a) && Object.isString(a[1])){
     193                                        a = a[1].split(',').last();
     194                                        if(a == b.identify()) this.open.bind(this).delay(1);
     195                                }
     196                        }
     197                }else if(Object.isHash(c))
     198                        this.steps = {
     199                                i:0,
     200                                k:c.keys(),
     201                                v:c.values(),
     202                                m:c.size()
     203                        };
     204                this.attachEvents();
     205                if(this.opt.autoOpen) this.open();
     206        },
     207        exec:function(bool){
     208                return Dialogs._open == this._open && Dialogs.elm('overlay').visible() && bool;
     209        },
     210        attachEvents:function(){
     211                Event.observe(window, 'resize', this.setDimensions.bindAsEventListener(this));
     212                if(Dialogs.fix.scroll)
     213                        Event.observe(window, 'scroll', this.setScroll.bindAsEventListener(this));
     214                var handles = [];
     215                if(Object.isElement(this.opt.handle)) handles.push($(this.opt.handle));
     216                else if(Object.isArray(this.opt.handle)) this.opt.handle.each(function(handle){ handles.push($(handle)); });
     217                else if(Object.isString(this.opt.handle)) handles = $$(this.opt.handle);
     218                handles.invoke('show').invoke('observe', 'click', function(e){
     219                        e.stop();
     220                        if(Object.isFunction(this.opt.afterClick)) this.opt.afterClick(e);
     221                        this.open();
     222                }.bindAsEventListener(this));
     223                Dialogs.elm('close').observe('click', function(){
     224                        if(this.exec(this.opt.close.link)) this.close();
     225                }.bindAsEventListener(this));
     226                Dialogs.elm('overlay').observe('click', function(){
     227                        if(this.exec(this.opt.close.overlay)) this.close();
     228                }.bindAsEventListener(this));
     229                document.observe('keyup', function(e){
     230                        if(this.exec(this.opt.close.esc && (e.which || e.keyCode) == Event.KEY_ESC)) this.close();
     231                }.bindAsEventListener(this));
     232                if(this.steps){
     233                        [Dialogs.elm('prev'), Dialogs.elm('next')].invoke('observe', 'click', this.setSteps.bindAsEventListener(this));
     234                        document.observe('keydown', function(e){
     235                                var c = e.which || e.keyCode;
     236                                if(this.exec((c == Event.KEY_LEFT) || (c == Event.KEY_RIGHT))) this.setSteps(e);
     237                        }.bindAsEventListener(this));
     238                }
     239        },
     240        setAuto:function(){
     241                this.auto = {max:0};
     242                var t = Dialogs.elm('title'), c = Dialogs.elm('close');
     243                [t,c].invoke('setStyle', 'float:none');
     244                $w('top content bottom').each(function(b){
     245                        var e = Dialogs.elm(b);
     246                        if(!e.visible()) this.auto[b] = {width:0,height:0};
     247                        else{
     248                                e.writeAttribute('style', 'display:inline;float:left;overflow:visible;white-space:nowrap');
     249                                this.auto[b] = e.getDimensions();
     250                                e.writeAttribute('style', 'overflow:hidden');
     251                                if(b == 'content') this.auto[b].width += (parseInt(this.opt.padding) || 0) * 2;
     252                                if(this.auto[b].width > this.auto.max) this.auto.max = this.auto[b].width;
     253                        }
     254                }.bind(this));
     255                t.setStyle('float:left');
     256                c.setStyle('float:right');
     257        },
     258        setDimensions:function(){
     259                if(!this.exec(true)) return;
     260                this.setAuto();
     261                var a = this.auto,
     262                    d = Dialogs.view(),
     263                    t = Dialogs.elm('content'),
     264                        c = Dialogs.elm('container'),
     265                    o = {
     266                          m:((parseInt(this.opt.margin) || 0) * 2),
     267                          p:((parseInt(this.opt.padding) || 0) * 2),
     268                          t:a.top.height,
     269                          b:a.bottom.height
     270                        },
     271                    m = {width:(d.width-o.m), height:(d.height-o.m-o.t-o.b)},
     272                    h = this.opt.height,
     273                        w = this.opt.width,
     274                    x = y = false;
     275                if(Object.isNumber(w)) w += o.p;
     276                if(w == 'max') w = m.width;
     277                if(!Object.isNumber(w)) w = a.max;
     278                if(w < (this.opt.minWidth || 0)) w = this.opt.minWidth || 0;
     279                if(w > m.width){ w = m.width; x = true }
     280                t.setStyle('width:'+(w-o.p)+'px;height:auto');
     281                if(Object.isNumber(h)) h += o.p;
     282                if(h == 'max') h = m.height;
     283                if(!Object.isNumber(h)) h = t.getHeight()+o.p;
     284                if(h < (this.opt.minHeight || 0)) w = this.opt.minHeight || 0;
     285                if(h > m.height){ h = m.height; y = true; }
     286                t.setStyle('height:'+(h-o.p)+'px;padding:'+(o.p/2)+'px');
     287//              dh: commented out to seperate overflow-x and overflow-y
     288//              if(this.opt.innerScroll && (x || y)) t.setStyle('overflow:scroll');
     289                if(this.opt.innerScroll && x) t.setStyle('overflow-x:scroll');
     290                if(this.opt.innerScroll && y) t.setStyle('overflow-y:scroll');
     291                var s = {w:w,h:(h+o.t+o.b)};
     292                c.setStyle('width:'+s.w+'px;height:'+s.h+'px;top:50%;left:50%;margin:-'+parseInt(s.h/2)+'px 0 0 -'+parseInt(s.w/2)+'px');
     293                if(Dialogs.fix.scroll){
     294                        Dialogs.elm('overlay').setStyle('width:'+d.width+'px;height:'+d.height+'px');
     295                        this.setScroll();
     296                }
     297        },
     298        setScroll:function(){
     299                if(!this.exec(true)) return;
     300                var v = Dialogs.view(),
     301                        c = Dialogs.elm('container'),
     302                        d = c.getDimensions(),
     303                        t = v.top + parseInt((v.height - d.height) / 2),
     304                        l = v.left + parseInt((v.width - d.width) / 2);
     305                c.setStyle('margin:0;top:'+t+'px;left:'+l+'px');
     306                Dialogs.elm('overlay').setStyle('margin:'+v.top+'px 0 0 '+v.left+'px');
     307        },
     308        setLoad:function(){
     309                var l = Dialogs.elm('loading').show(),
     310                    t = Dialogs.elm('content'),
     311                    b = t.down('#'+l.identify());
     312                if(!Object.isElement(b)) t.insert(l);
     313        },
     314        setAjax:function(){
     315                this.setLoad();
     316                var o = this.opt.ajax.options || {},
     317                    c = (o.onComplete && Object.isFunction(o.onComplete) ? o.onComplete : null),
     318                    a = function(t){
     319                                var tpl = this.opt.ajax.jsonTemplate;
     320                                if(t.responseJSON && Object.isString(tpl)) Dialogs.elm('content').update(tpl.interpolate(t.responseJSON));
     321                                else Dialogs.elm('content').update(t.responseText || '');
     322                                this.setImages();
     323                                this.setDimensions();
     324                                if(Object.isFunction(c)) c(t);
     325                        }.bind(this);
     326                Object.extend(o, {onComplete:a});
     327                new Ajax.Request(this.opt.ajax.url, o);
     328        },
     329        setIframe:function(){
     330                this.setLoad();
     331                var f = new Element('iframe', {src:this.opt.iframe, frameborder:0, id:'dialog-iframe'});
     332                Dialogs.elm('content').insert(f);
     333                f.observe('load', function(){
     334                        Dialogs.elm('loading').hide();
     335                        f.setStyle('width:100%;height:100%');
     336                        this.setDimensions();
     337                        if(Object.isFunction(this.opt.afterIframeLoad)) this.opt.afterIframeLoad();
     338                }.bindAsEventListener(this));
     339        },
     340        setSteps:function(ev){
     341                if(!this.exec(true)) return;
     342                var m = this.steps.m,
     343                    s = false,
     344                        n = Dialogs.elm('next'),
     345                        p = Dialogs.elm('prev');
     346                if((ev.which || ev.keyCode) == Event.KEY_RIGHT || ev.element().hasClassName('next')){
     347                        if(this.steps.i < (m - 1)) s = true;
     348                        if(s) ++this.steps.i;
     349                        if(((this.steps.i + 1) >= m) && n.visible()) n.hide();
     350                        if(((this.steps.i - 1) >= 0) && !p.visible()) p.show();
     351                }else{
     352                        if(this.steps.i > 0) s = true;
     353                        if(s) --this.steps.i;
     354                        if(((this.steps.i - 1) < 0) && p.visible()) p.hide();
     355                        if(((this.steps.i + 1) <= m) && !n.visible()) n.show();
     356                }
     357                if(s) this.setContent();
     358        },
     359        setContent:function(){
     360                var c = this.opt.content,
     361                    t = Dialogs.elm('content');
     362                t.update('');
     363                if(Object.isString(c) || Object.isElement(c)) t.insert(c);
     364                else if(Object.isArray(c)) c.each(function(b){ t.insert(b); });
     365                else if(Object.isHash(c)){
     366                        var b = Dialogs.elm('bottom');
     367                        t.update('').insert(this.steps.v[this.steps.i]);
     368                        Dialogs.elm('curr').update(this.steps.k[this.steps.i]);
     369                        if(!b.visible()) b.show().childElements().invoke('show');
     370                        if(this.steps.i <= 0) Dialogs.elm('prev').hide();
     371                        if(this.steps.i >= (this.steps.m - 1)) Dialogs.elm('next').hide();
     372                }else if(Object.isString(this.opt.ajax.url)) this.setAjax();
     373                else if(Object.isString(this.opt.iframe)) this.setIframe();
     374                this.setImages();
     375                this.setDimensions.bind(this).defer();
     376        },
     377        setImages:function(){
     378                Dialogs.elm('content').select('img').each(function(el){
     379                        el.onload = function(){
     380                                this.setDimensions();
     381                        }.bind(this);
     382                }.bind(this));
     383        },
     384        open:function(){
     385                if(Dialogs.fix.select)
     386                        $$('select').select(function(el){ return el.visible(); }).invoke('hide').invoke('addClassName', 'dialog-hideselect');
     387                if(Object.isString(this.opt.title) || this.opt.close.link){
     388                        if(Object.isString(this.opt.title)) Dialogs.elm('title').show().update(this.opt.title);
     389                        if(this.opt.close.link) Dialogs.elm('close').show();
     390                        else Dialogs.elm('close').hide();
     391                        Dialogs.elm('top').show();
     392                }else Dialogs.elm('top').hide();
     393                var o = Dialogs.elm('overlay'), c = Dialogs.elm('container'), t = Dialogs.elm('content');
     394                [o, c, t].invoke('show');
     395//              dh: commented out background so it can be set via class
     396//              o.setOpacity(this.opt.opacity || 1).setStyle({background:this.opt.background[0] || '#000'});
     397                o.setOpacity(this.opt.opacity || 1);
     398//              dh: commented out background so it can be set via class
     399//              c.writeAttribute('style', 'left:-99999px;top:-99999px;background:'+(this.opt.background[1] || '#fff'));
     400                c.writeAttribute('style', 'left:-99999px;top:-99999px;');
     401                t.writeAttribute('class', this.opt.className || '');
     402                Dialogs._open = new Date().getTime();
     403                this._open = Dialogs._open;
     404                this.setContent();
     405                if(Object.isFunction(this.opt.afterOpen)) this.opt.afterOpen();
     406        },
     407        close:function(){
     408                Dialogs.close();
     409                if(Object.isFunction(this.opt.afterClose)) this.opt.afterClose();
     410        }
     411};
  • modules/_shared/tmpl/default/header.php

    diff --git a/modules/_shared/tmpl/default/header.php b/modules/_shared/tmpl/default/header.php
    index 691d059..2d16bc0 100644
    a b EOF; 
    7070    </script>
    7171
    7272    <link rel="stylesheet" type="text/css" href="js/prototip/prototip.css">
     73    <link rel="stylesheet" type="text/css" href="js/dialog/dialog.css">
    7374    <link rel="stylesheet" type="text/css" href="<?php echo skin_url ?>/style.css">
    7475    <link rel="stylesheet" type="text/css" href="<?php echo skin_url ?>/header.css">
    7576    <link rel="stylesheet" type="text/css" href="<?php echo skin_url ?>/menus.css">
    EOF; 
    8485
    8586    <script type="text/javascript" src="js/prototype.js"></script>
    8687    <script type="text/javascript" src="js/prototip/prototip.js"></script>
     88    <script type="text/javascript" src="js/dialog/dialog.js"></script>
    8789
    8890    <script type="text/javascript" src="js/utils.js"></script>
    8991    <script type="text/javascript" src="js/AC_OETags.js"></script>
  • new file modules/tv/lookup_metadata.php

    diff --git a/modules/tv/lookup_metadata.php b/modules/tv/lookup_metadata.php
    new file mode 100644
    index 0000000..ca9cfbf
    - +  
     1<?php
     2/**
     3 * Does a query against the backend to look up metadata for a show
     4 * returns the result as JSON
     5 *
     6 * @license     GPL
     7 *
     8 * @package     MythWeb
     9 * @subpackage  TV
     10 *
     11/**/
     12
     13    header('Content-Type: application/json');
     14
     15    $url = "Video/LookupVideo";
     16    $args = array(
     17                   'Title'        => $_REQUEST['title'],
     18                   'Subtitle'     => $_REQUEST['subtitle'],
     19                   'Inetref'      => $_REQUEST['inetref'],
     20                   'Season'       => $_REQUEST['season'],
     21                   'Episode'      => $_REQUEST['episode'],
     22                   'GrabberType'  => $_REQUEST['grabbertype']);
     23
     24    echo MythBackend::find()->httpRequestAsJson($url, $args);
  • modules/tv/tmpl/default/_advanced_options.php

    diff --git a/modules/tv/tmpl/default/_advanced_options.php b/modules/tv/tmpl/default/_advanced_options.php
    index 421e9ae..812ac82 100644
    a b  
    2424                        );
    2525    }
    2626
     27    // Tries to populate the inetref, season and episode fields
     28    // by doing a metadata lookup against the backend.  If multiple
     29    // results are return displays a dialog to let the user choose
     30    // the appropriate show
     31    function lookupMetadata(success, failure) {
     32        ajax_add_request();
     33
     34        new Ajax.Request('<?php echo root_url ?>tv/lookup_metadata',
     35                         {
     36                            parameters: {
     37                                              'title'    : "<?php echo $schedule->title ?>",
     38                                              'subtitle' : "<?php echo $schedule->subtitle ?>",
     39                                              'inetref'  : $("inetref").value,
     40                                              'season'   : $("season").value,
     41                                              'episode'  : $("episode").value
     42                                        },
     43                            asynchronous: true,
     44                            method: 'get',
     45                            onSuccess: success,
     46                            onFailure: failure
     47                         }
     48                        );
     49    }
     50
     51    // callback for when metadata is returned for this show
     52    function onMetadata(transport) {
     53        ajax_remove_request();
     54
     55        // make sure we got valid date
     56        if (!transport || !transport.responseJSON || !transport.responseJSON.VideoLookupList) {
     57           messageDialog("<?php echo t("Metadata Lookup Error")?>",
     58                         "<?php echo t("Server returned invalid data when attempting to retrieve metadata.")?>");
     59        }
     60
     61        var list = transport.responseJSON.VideoLookupList;
     62
     63        // display an error if there's no data
     64        if (list.Count == 0) {
     65            messageDialog("<?php echo t("Metadata Lookup")?>", "<?php echo t("No metadata results found.")?>");
     66
     67        // populate the data immediately if there is one result
     68        } else  if (list.Count == 1) {
     69          updateMetadata(list.VideoLookups[0]);
     70
     71        // if we can pick the right item from the list then use it
     72        // otherwise display a dialog for the user to choose which result
     73        } else {
     74           var item = guessItem(list);
     75           if (item) {
     76                updateMetadata(item);
     77           } else {
     78                multipleResultDialog(list);
     79           }
     80        }
     81    }
     82
     83    // tries to find the correct item in list based off of the TMSref
     84    // if we can find it, cool, if not return null
     85    function guessItem(list) {
     86        var tmsRef = "<?php echo $schedule->seriesid ?>";
     87        for (var i=0; i < list.VideoLookups.length; i++) {
     88            var item = list.VideoLookups[i];
     89            if (tmsRef && item.TMSRef == tmsRef) {
     90               return item;
     91            }
     92        }
     93        return null;
     94    }
     95
     96    // updates the inetref, season & episode values on the page
     97    // optionally creates or updates a "metdata home page" link
     98    // in the "More" section of the page
     99    function updateMetadata(item) {
     100         $("inetref").value = item.InetRef;
     101         $("season").value = item.Season;
     102         $("episode").value = item.Episode;
     103
     104         // if the item has a real HomePage then update it
     105         if (!!item.HomePage) {
     106             updateHomePage(item);
     107
     108         // otherwise do a lookup again
     109         } else {
     110             lookupMetadata(onHomePage, onMetadataFailure);
     111         }
     112
     113    }
     114
     115    function onHomePage(transport) {
     116        ajax_remove_request();
     117
     118        var fakeItem = {HomePage: ""};
     119
     120        // make sure we got valid data; if not ignore
     121        if (!transport || !transport.responseJSON || !transport.responseJSON.VideoLookupList) {
     122            updateHomePage(fakeItem);
     123            return;
     124        }
     125
     126        var list = transport.responseJSON.VideoLookupList;
     127
     128        // ignore if there's no data
     129        if (list.Count == 0) {
     130            updateHomePage(fakeItem);
     131            return;
     132
     133        // populate the data immediately if there is one result
     134        } else  if (list.Count == 1) {
     135           updateHomePage(list.VideoLookups[0]);
     136
     137        // if we can pick the right item from the list then use it
     138        // otherwise hich result
     139        } else {
     140           var item = guessItem(list);
     141           if (item) {
     142                updateMetadata(item);
     143           } else {
     144                updateHomePage(fakeItem);
     145           }
     146        }
     147
     148
     149    }
     150
     151    // displays a dialog with an image and title of each possible show
     152    // if the user clicks on one of them then populates the metadata in the page
     153    function multipleResultDialog(list) {
     154        // parent div for the result
     155        var parent = new Element("div", {"class": "multiple-metadata"});
     156
     157        // add all of the results
     158        parent.insert(generateResults(list));
     159
     160        // add a cancel "button" to exit without choosing an option
     161        var a = new Element("a", {}).update("<?php echo t("Cancel") ?>");
     162        Event.observe(a, "click", function() { Dialogs.close(); });
     163        var d = new Element("div", {"class": "commands"});
     164        d.insert(a);
     165        parent.insert(d);
     166
     167        // display the dialog
     168        new Dialog({
     169                opacity: 0.9,
     170                title: "<?php echo t("Select the correct show")?>",
     171                content: parent
     172                }).open();
     173    }
     174
     175    // returns a div with a list of all of the items neatly formatted
     176    function generateResults(list) {
     177        var div = new Element("div", {"class": "metadata-list"});
     178
     179        for (var i=0; i < list.VideoLookups.length; i++) {
     180            div.insert(generateResultsItem(list.VideoLookups[i]));
     181        }
     182
     183       return div;
     184    }
     185
     186    // returns a div for a single result
     187    // includes hover and click event handlers
     188    function generateResultsItem(item) {
     189        var div = new Element("div", {"class": "metadata-item"});
     190        Event.observe(div, "mouseover", function(e) { this.addClassName("hover");});
     191        Event.observe(div, "mouseout", function(e) { this.removeClassName("hover");});
     192        Event.observe(div, "click", function(e) { updateMetadata(item); Dialogs.close();});
     193        var img = generateItemImg(item);
     194        div.insert(img);
     195        var title = new Element("div", {"class": "title"});
     196        var titleString = item.Title;
     197        if (item.Year && item.Year > 0) {
     198            var suffix = " (" + item.Year + ")";
     199            titleString = titleString.endsWith(suffix) ? titleString : titleString + suffix;
     200        }
     201 
     202        title.update(titleString);
     203        div.insert(title);
     204
     205        var desc = new Element("div", {"class": "description"});
     206        var descString = item.Description.length > 450 ? item.Description.substring(0, 450) + "..." : item.Description;
     207        desc.update(descString);
     208        div.insert(desc);
     209
     210        return div;
     211    }
     212
     213    // generates an image or empty div based on if there is
     214    // any thumbnail art work for this item
     215    function generateItemImg(item) {
     216        if (item.Artwork && item.Artwork.length) {
     217             var art = item.Artwork[0];
     218             var thumbUrl = art.Thumbnail;
     219           
     220             // hack to allow proxying of ttvdb.com images since they don't allow hot linking
     221             if (<?php echo $_SERVER['HTTPS'] == 'on' ? "false" : "true"?> &&  thumbUrl.startsWith("http://www.thetvdb.com")) {
     222                 thumbUrl = "<?php echo root_url ?>tv/ttvdb_proxy?url=" + thumbUrl.substring(22);
     223             }
     224
     225             return new Element("img", {src: thumbUrl, "class": art.Type});
     226        }
     227        return new Element("div", {"class": "no-art"});
     228    }
     229
     230    // callback for failure contacting the server
     231    function onMetadataFailure(response) {
     232        ajax_remove_request();
     233        messageDialog("<?php echo t("Metadata Lookup Error")?>", "<?php echo t("Error contacting server to retrieve metadata.")?>");
     234    }
     235
     236    // displays a dialog with a message in it and an OK button
     237    function messageDialog(title, msg) {
     238        $("metadata-message").update(msg);
     239        new Dialog({
     240                opacity: 0.9,
     241                title: title,
     242                target:{
     243                    id:'message-dialog',
     244                    auto:true
     245                }
     246                }).open();
     247
     248    }
     249
     250    // Hook to start up the Dialog JS
     251    Dialogs.load();
     252
    27253// -->
    28254</script>
    29 
    30255            <h3><?php echo t('Advanced Options') ?>:</h3>
    31256            (<?php
    32                 echo '<a href="#" onclick="toggle_advanced(false); return false;" id="hide_advanced"';
     257                echo '<a onclick="toggle_advanced(false); return false;" id="hide_advanced"';
    33258                if (!$_SESSION['tv']['show_advanced_schedule'])
    34259                    echo ' style="display: none"';
    35260                echo '>', t('Hide'), '</a>',
     
    133358                <dt><?php echo t('Preferred Input') ?>:</dt>
    134359                <dd><?php input_select($schedule->prefinput, 'prefinput') ?></dd>
    135360                <dt><?php echo t('Internet Reference #') ?>:</dt>
    136                 <dd><input type="text" name="inetref" value="<?php echo html_entities($schedule->inetref) ?>"></dd>
     361                <dd class="commands"><input id="inetref" class="inetref" type="text" name="inetref" value="<?php echo html_entities($schedule->inetref) ?>"><a onclick="lookupMetadata(onMetadata, onMetadataFailure); return false;"><?php echo t("Look up Metadata")?></a></dd>
    137362                <dt><?php echo t('Season') ?>:</dt>
    138                 <dd><input type="text" class="quantity" name="season" value="<?php echo html_entities($schedule->season) ?>"></dd>
     363                <dd><input type="text" id="season" class="quantity" name="season" value="<?php echo html_entities($schedule->season) ?>"></dd>
    139364                <dt><?php echo t('Episode') ?>:</dt>
    140                 <dd><input type="text" class="quantity" name="episode" value="<?php echo html_entities($schedule->episode) ?>"></dd>
     365                <dd><input type="text" id="episode" class="quantity" name="episode" value="<?php echo html_entities($schedule->episode) ?>"></dd>
    141366                <dt><label for="autometadata"><?php echo t('Look up Metadata') ?>:</label></dt>
    142367                <dd><input type="checkbox" class="radio" id="autometadata" name="autometadata"<?php if ($schedule->autometadata) echo ' CHECKED' ?> value="1"></dd>
    143368                <dt><label for="autocommflag"><?php echo t('Auto-flag commercials') ?>:</label></dt>
     
    167392                <dd><input type="text" class="quantity" name="endoffset" value="<?php echo html_entities($schedule->endoffset) ?>">
    168393                    <?php echo t('minutes') ?></dd>
    169394            </dl>
     395
     396<div style="display: none;" id="message-dialog">
     397   <div id="metadata-message"></div>
     398   <div class="commands">
     399     <a onclick="Dialogs.close(); return false;"><?php echo t('OK') ?></a>
     400   </div>
     401</div>
  • modules/tv/tmpl/default/detail.php

    diff --git a/modules/tv/tmpl/default/detail.php b/modules/tv/tmpl/default/detail.php
    index 71ed1ea..82914fd 100644
    a b  
    9494                         parameters: {exit: 1,
    9595                                      host: host,
    9696                                      chanid: chanid,
    97                                       starttime: starttime,
     97                                      starttime: starttime
    9898                                      }
    9999                        }
    100100                        );
    101101    }
    102102
     103    // Tries to find metadata for the current item
     104    // If found adds a "Home Page" link to the page
     105    function detailLookupMetadata() {
     106        new Ajax.Request('<?php echo root_url ?>tv/lookup_metadata',
     107                         {
     108                            parameters: {
     109                                              'title'    : "<?php echo $schedule->title ?>",
     110                                              'subtitle' : "<?php echo $schedule->subtitle ?>",
     111                                              'inetref'  : "<?php echo ($program ? $program->inetref : $schedule->inetref) ?>",
     112                                              'season'   : "<?php echo ($program ? $program->season : $schedule->season) ?>",
     113                                              'episode'  : "<?php echo ($program ? $program->episode : $schedule->episode) ?>"
     114                                        },
     115                            asynchronous: true,
     116                            method: 'get',
     117                            onSuccess: detailOnMetadata,
     118                            onFailure: detailOnMetadataFailure
     119                         }
     120                        );
     121
     122    }
     123
     124    // if metadata is found inserts a home page then update the links
     125    function detailOnMetadata(transport) {
     126        var list = transport.responseJSON.VideoLookupList;
     127     
     128        updateHomePage(list.VideoLookups[0] || {});
     129
     130    }
     131
     132    // silently fail (no need to disrupt the page)
     133    function detailOnMetadataFailure(transport) {
     134    }
     135
     136    function updateHomePage(item) {
     137         var homePage = $("home-page");
     138         var homeButton = $("metadata-home-page-link");
     139
     140         // if this item doesn't have a home page link then
     141         // remove the existing link or ignore
     142         if (!item.HomePage) {
     143             homePage && Element.remove(homePage);
     144             homeButton && Element.remove(homeButton);
     145             return;
     146         }
     147
     148         // update the link or create it if this item does have a home page
     149         if (homePage) {
     150              homePage.href = item.HomePage;
     151         } else {
     152              $($$(".x-links")[0].children[1]).insert({top:
     153                  new Element("a",
     154                      {href: item.HomePage,
     155                       target: "_new", id: "home-page"}).
     156                      update(item.Title + " " + "<?php echo t("Metadata Home Page") ?>")});
     157         }
     158
     159         if (homeButton) {
     160              homeButton.href = item.HomePage;
     161         }  else {
     162              var mhp = $("metadata-home-page");
     163              mhp && mhp.insert(
     164                         new Element("a",
     165                              {id: "metadata-home-page-link",
     166                               href: item.HomePage,
     167                               target: "_new"}).
     168                              update("<?php echo t("Metadata Home Page")?>")
     169                          );
     170         }
     171
     172
     173    }
     174
     175
     176    // hook to look up data once the page has started
     177    detailLookupMetadata();
    103178// -->
    104179</script>
    105180
     
    214286            if (strlen($program->inetref) > 0) {
    215287        ?><tr class="x-extras">
    216288            <th><?php echo t('Internet Reference #') ?>:</th>
    217             <td><?php echo $program->inetref ?></td>
     289            <td><?php echo $program->inetref ?> <span class="commands" id="metadata-home-page"></span></td>
    218290        </tr><?php
    219291            }
    220292            if ($program->season > 0) {
  • modules/tv/tmpl/default/recorded.php

    diff --git a/modules/tv/tmpl/default/recorded.php b/modules/tv/tmpl/default/recorded.php
    index d7de684..d0b483f 100644
    a b  
    7676</tr>
    7777</table>
    7878</form>
    79 
    8079<table id="recorded_list" border="0" cellpadding="0" cellspacing="0" class="list small">
    8180<tr class="menu">
    8281    <td class="list"<?php if ($group_field) echo ' colspan="2"' ?>>&nbsp;</td>
  • new file modules/tv/ttvdb_proxy.php

    diff --git a/modules/tv/ttvdb_proxy.php b/modules/tv/ttvdb_proxy.php
    new file mode 100644
    index 0000000..ba1433c
    - +  
     1<?php
     2/**
     3 * Proxies requests to ttvdb.com so that we can grab images from their site
     4 * Tries to avoid being a total open proxy by only proxying to thetvdb.com
     5 *
     6 * @license     GPL
     7 *
     8 * @package     MythWeb
     9 * @subpackage  TV
     10 *
     11/**/
     12
     13    header('Content-Type: image/jpg');
     14
     15    echo @file_get_contents("http://www.thetvdb.com" . $_REQUEST['url']);
  • skins/default/style.css

    diff --git a/skins/default/style.css b/skins/default/style.css
    index 2fbb26e..7353351 100644
    a b  
    235235#feed_buttons a {
    236236    padding-right:      1em;
    237237}
     238
     239#dialog-overlay {
     240    background-color:   #506090;
     241}
     242
     243#dialog-top {
     244    background-color:   #203670;
     245    border:             1px solid #203670;
     246}
     247
     248#dialog-title {
     249    color:              white;
     250
     251}
     252
     253#dialog-content {
     254    background-color:   #265990;
     255    text-align:         center;
     256}
  • skins/default/tv_detail.css

    diff --git a/skins/default/tv_detail.css b/skins/default/tv_detail.css
    index 2148bf7..ca84088 100644
    a b  
    270270    color:              #F0F000;
    271271    text-decoration:    underline;
    272272}
     273
     274#metadata-home-page {
     275    margin-left:        20px;
     276}
  • skins/default/tv_schedule.css

    diff --git a/skins/default/tv_schedule.css b/skins/default/tv_schedule.css
    index b1a7ed9..2740816 100644
    a b  
    5656    width:     18em;
    5757}
    5858
     59#schedule input.inetref {
     60    width:         5em !important;
     61    margin-right:  20px;   
     62}
     63
    5964/* A special subclass for options with extra-long input fields */
    6065#schedule .x-options dl.x-long input, .x-options dl.x-long textarea {
    6166    width:     32em;
     
    7378    padding:        .5em 0 1em 0;
    7479}
    7580
     81/* Metadata specific classes */
     82
     83#metadata-message {
     84    margin-bottom:      10px;
     85}
     86
     87.multiple-metadata {
     88}
     89
     90.metadata-item {
     91    width:              500px;
     92    min-height:         150px;
     93    margin:             0px 15px 10px 0px;
     94    border:             1px solid white;
     95}
     96
     97.metadata-item.hover {
     98   background-color:    white;
     99   cursor:              pointer;
     100   color:               #265990;
     101}
     102
     103.metadata-item img {
     104   float:               left;
     105}
     106
     107.metadata-item img.coverart {
     108   height:              150px;
     109   width:               100px;
     110   margin-right:        10px;
     111}
     112
     113.metadata-item img.fanart {
     114   height:              150px;
     115   width:               100px;
     116   margin-right:        10px;
     117}
     118
     119.metadata-item img.banner {
     120   height:              55px;
     121   width:               300px;
     122   margin:              5px auto -10px;
     123   float:               none;
     124}
     125
     126.metadata-item .no-art {
     127   height:              2em;
     128}
     129
     130
     131.metadata-item .title {
     132   font-size:           2em;
     133   font-weight:         bold;
     134   padding-top:         .5em;
     135}
     136
     137.metadata-item .description {
     138   padding:            0px 10px 10px;
     139   text-align:         left;
     140}