10 changed files with 719 additions and 5 deletions
@ -0,0 +1,34 @@ |
|||||
|
from .main import Charts |
||||
|
|
||||
|
|
||||
|
def autoload(): |
||||
|
return Charts() |
||||
|
|
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'charts', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'label': 'Charts', |
||||
|
'description': 'Displays selected charts on the home page', |
||||
|
'type': 'list', |
||||
|
'name': 'charts_providers', |
||||
|
'tab': 'display', |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'max_items', |
||||
|
'default': 5, |
||||
|
'type': 'int', |
||||
|
'description': 'Maximum number of items displayed from each chart.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'update_interval', |
||||
|
'default': 12, |
||||
|
'type': 'int', |
||||
|
'advanced': True, |
||||
|
'description': '(hours)', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}] |
@ -0,0 +1,60 @@ |
|||||
|
import time |
||||
|
|
||||
|
from couchpotato import tryInt |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.api import addApiView |
||||
|
from couchpotato.core.event import addEvent,fireEvent |
||||
|
from couchpotato.core.plugins.base import Plugin |
||||
|
|
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class Charts(Plugin): |
||||
|
|
||||
|
update_in_progress = False |
||||
|
|
||||
|
def __init__(self): |
||||
|
addApiView('charts.view', self.automationView) |
||||
|
addEvent('app.load', self.setCrons) |
||||
|
|
||||
|
def setCrons(self): |
||||
|
fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.conf('update_interval', default = 12)) |
||||
|
self.updateViewCache() |
||||
|
|
||||
|
|
||||
|
def automationView(self, force_update = False, **kwargs): |
||||
|
|
||||
|
if force_update: |
||||
|
charts = self.updateViewCache() |
||||
|
else: |
||||
|
charts = self.getCache('charts_cached') |
||||
|
if not charts: |
||||
|
charts = self.updateViewCache() |
||||
|
|
||||
|
return { |
||||
|
'success': True, |
||||
|
'count': len(charts), |
||||
|
'charts': charts |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def updateViewCache(self): |
||||
|
|
||||
|
if self.update_in_progress: |
||||
|
while self.update_in_progress: |
||||
|
time.sleep(1) |
||||
|
catched_charts = self.getCache('charts_cached') |
||||
|
if catched_charts: |
||||
|
return catched_charts |
||||
|
|
||||
|
try: |
||||
|
self.update_in_progress = True |
||||
|
charts = fireEvent('automation.get_chart_list', merge = True) |
||||
|
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12))) |
||||
|
except: |
||||
|
log.error('Failed refreshing charts') |
||||
|
|
||||
|
self.update_in_progress = False |
||||
|
|
||||
|
return charts |
@ -0,0 +1,240 @@ |
|||||
|
.charts { |
||||
|
clear: both; |
||||
|
} |
||||
|
|
||||
|
.charts > h2 { |
||||
|
height: 40px; |
||||
|
} |
||||
|
|
||||
|
.charts .chart { |
||||
|
display: inline-block; |
||||
|
width: 50%; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
|
||||
|
.charts div.refresh { |
||||
|
margin-top: 10px; |
||||
|
clear:both; |
||||
|
text-align:center; |
||||
|
} |
||||
|
|
||||
|
.charts p.no_charts_enabled { |
||||
|
padding: 0.7em 1em; |
||||
|
} |
||||
|
|
||||
|
.charts div.refresh a { |
||||
|
display:block; |
||||
|
} |
||||
|
|
||||
|
.charts .chart h3 a { |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.charts .chart .media_result { |
||||
|
display: inline-block; |
||||
|
width: 100%; |
||||
|
height: 150px; |
||||
|
} |
||||
|
|
||||
|
@media all and (max-width: 960px) { |
||||
|
.charts .chart { |
||||
|
width: 50%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media all and (max-width: 600px) { |
||||
|
.charts .chart { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data { |
||||
|
left: 150px; |
||||
|
background: #4e5969; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info { |
||||
|
top: 10px; |
||||
|
left: 15px; |
||||
|
right: 15px; |
||||
|
bottom: 10px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info h2 { |
||||
|
white-space: normal; |
||||
|
max-height: 120px; |
||||
|
font-size: 18px; |
||||
|
line-height: 18px; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .rating, |
||||
|
.charts .chart .media_result .data .info .genres, |
||||
|
.charts .chart .media_result .data .info .year { |
||||
|
position: static; |
||||
|
display: block; |
||||
|
padding: 0; |
||||
|
opacity: .6; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .year { |
||||
|
margin: 10px 0 0; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .rating { |
||||
|
font-size: 20px; |
||||
|
float: right; |
||||
|
margin-top: -20px; |
||||
|
} |
||||
|
.charts .chart .media_result .data .info .rating:before { |
||||
|
content: "\e031"; |
||||
|
font-family: 'Elusive-Icons'; |
||||
|
font-size: 14px; |
||||
|
margin: 0 5px 0 0; |
||||
|
vertical-align: bottom; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .genres { |
||||
|
font-size: 11px; |
||||
|
font-style: italic; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .plot { |
||||
|
display: block; |
||||
|
font-size: 11px; |
||||
|
overflow: hidden; |
||||
|
text-align: justify; |
||||
|
height: 100%; |
||||
|
z-index: 2; |
||||
|
top: 64px; |
||||
|
position: absolute; |
||||
|
background: #4e5969; |
||||
|
cursor: pointer; |
||||
|
transition: all .4s ease-in-out; |
||||
|
padding: 0 3px 10px 0; |
||||
|
} |
||||
|
.charts .chart .media_result .data:before { |
||||
|
bottom: 0; |
||||
|
content: ''; |
||||
|
display: block; |
||||
|
height: 10px; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
bottom: 10px; |
||||
|
position: absolute; |
||||
|
background: linear-gradient( |
||||
|
0deg, |
||||
|
rgba(78, 89, 105, 1) 0%, |
||||
|
rgba(78, 89, 105, 0) 100% |
||||
|
); |
||||
|
z-index: 3; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data .info .plot.full { |
||||
|
top: 0; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .data { |
||||
|
cursor: default; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .options { |
||||
|
left: 150px; |
||||
|
} |
||||
|
.charts .chart .media_result .options select[name=title] { width: 100%; } |
||||
|
.charts .chart .media_result .options select[name=profile] { width: 100%; } |
||||
|
.charts .chart .media_result .options select[name=category] { width: 100%; } |
||||
|
|
||||
|
.charts .chart .media_result .button { |
||||
|
position: absolute; |
||||
|
margin: 2px 0 0 0; |
||||
|
right: 15px; |
||||
|
bottom: 15px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.charts .chart .media_result .thumbnail { |
||||
|
width: 100px; |
||||
|
position: absolute; |
||||
|
left: 50px; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result div.chart_number { |
||||
|
color: white; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
padding: 10px; |
||||
|
font: bold 2em/1em Helvetica, Sans-Serif; |
||||
|
width: 50px; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result div.chart_number.chart_in_wanted { |
||||
|
background: rgb(0, 255, 40); /* fallback color */ |
||||
|
background: rgba(0, 255, 40, 0.3); |
||||
|
} |
||||
|
.charts .chart .media_result div.chart_number.chart_in_library { |
||||
|
background: rgb(0, 202, 32); /* fallback color */ |
||||
|
background: rgba(0, 202, 32, 0.3); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.charts .chart .media_result .actions { |
||||
|
position: absolute; |
||||
|
top: 10px; |
||||
|
right: 10px; |
||||
|
display: none; |
||||
|
width: 90px; |
||||
|
} |
||||
|
.charts .chart .media_result:hover .actions { |
||||
|
display: block; |
||||
|
} |
||||
|
.charts .chart .media_result:hover h2 .title { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
.charts .chart .media_result .data.open .actions { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.charts .chart .media_result .actions a { |
||||
|
margin-left: 10px; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.toggle_menu { |
||||
|
height: 50px; |
||||
|
} |
||||
|
|
||||
|
.toggle_menu a { |
||||
|
display: block; |
||||
|
width: 50%; |
||||
|
float: left; |
||||
|
color: rgba(255,255,255,.6); |
||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.0666667); |
||||
|
} |
||||
|
|
||||
|
.toggle_menu a:hover { |
||||
|
border-color: #047792; |
||||
|
border-width: 4px; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.toggle_menu a.active { |
||||
|
border-bottom: 4px solid #04bce6; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.toggle_menu a:last-child { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
.toggle_menu h2 { |
||||
|
height: 40px; |
||||
|
} |
||||
|
|
@ -0,0 +1,166 @@ |
|||||
|
var Charts = new Class({ |
||||
|
|
||||
|
Implements: [Options, Events], |
||||
|
|
||||
|
initialize: function(options){ |
||||
|
var self = this; |
||||
|
self.setOptions(options); |
||||
|
|
||||
|
self.create(); |
||||
|
}, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el_refreshing_text = new Element('span.refreshing', { |
||||
|
'text': 'Refreshing charts...' |
||||
|
}); |
||||
|
|
||||
|
self.el_refresh_link = new Element('a.refresh', { |
||||
|
'href': '#', |
||||
|
'text': 'Refresh charts', |
||||
|
'events': { |
||||
|
'click': function(e) { |
||||
|
e.preventDefault(); |
||||
|
self.el.getChildren('div.chart').destroy(); |
||||
|
self.el_refreshing_text.show(); |
||||
|
self.el_refresh_link.hide(); |
||||
|
self.api_request = Api.request('charts.view', { |
||||
|
'data': { 'force_update': 1 }, |
||||
|
'onComplete': self.fill.bind(self) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}).hide(); |
||||
|
|
||||
|
self.el_refresh_container = new Element('div.refresh').grab( |
||||
|
self.el_refreshing_text |
||||
|
).grab(self.el_refresh_link); |
||||
|
|
||||
|
self.el_no_charts_enabled = new Element('p.no_charts_enabled', { |
||||
|
'html': 'Hey, it looks like you have no charts enabled at the moment. If you\'d like some great movie suggestions you can go to <a href="' + App.createUrl('settings/display') + '">settings</a> and turn on some charts of your choice.' |
||||
|
}).hide(); |
||||
|
|
||||
|
self.el = new Element('div.charts').grab( |
||||
|
self.el_no_charts_enabled |
||||
|
).grab(self.el_refresh_container); |
||||
|
|
||||
|
if( Cookie.read('suggestions_charts_menu_selected') === 'charts') |
||||
|
self.el.show(); |
||||
|
else |
||||
|
self.el.hide(); |
||||
|
|
||||
|
self.api_request = Api.request('charts.view', { |
||||
|
'onComplete': self.fill.bind(self) |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
fill: function(json){ |
||||
|
|
||||
|
var self = this; |
||||
|
|
||||
|
self.el_refreshing_text.hide(); |
||||
|
self.el_refresh_link.show(); |
||||
|
|
||||
|
if(!json || json.count == 0){ |
||||
|
self.el_no_charts_enabled.show(); |
||||
|
self.el_refresh_link.show(); |
||||
|
self.el_refreshing_text.hide(); |
||||
|
} |
||||
|
else { |
||||
|
self.el_no_charts_enabled.hide(); |
||||
|
|
||||
|
json.charts.sort(function(a, b) { |
||||
|
return a.order - b.order; |
||||
|
}); |
||||
|
|
||||
|
Object.each(json.charts, function(chart){ |
||||
|
|
||||
|
var c = new Element('div.chart').grab( |
||||
|
new Element('h3').grab( new Element('a', { |
||||
|
'text': chart.name, |
||||
|
'href': chart.url |
||||
|
})) |
||||
|
); |
||||
|
|
||||
|
var it = 1; |
||||
|
|
||||
|
Object.each(chart.list, function(movie){ |
||||
|
|
||||
|
var m = new Block.Search.MovieItem(movie, { |
||||
|
'onAdded': function(){ |
||||
|
self.afterAdded(m, movie) |
||||
|
} |
||||
|
}); |
||||
|
var in_database_class = movie.in_wanted ? '.chart_in_wanted' : (movie.in_library ? '.chart_in_library' : ''); |
||||
|
var in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : ''); |
||||
|
m.el.grab( new Element('div.chart_number' + in_database_class, { 'text': it++, 'title': in_database_title })); |
||||
|
m.data_container.grab( |
||||
|
new Element('div.actions').adopt( |
||||
|
new Element('a.add.icon2', { |
||||
|
'title': 'Add movie with your default quality', |
||||
|
'data-add': movie.imdb, |
||||
|
'events': { |
||||
|
'click': m.showOptions.bind(m) |
||||
|
} |
||||
|
}), |
||||
|
$(new MA.IMDB(m)), |
||||
|
$(new MA.Trailer(m, { |
||||
|
'height': 150 |
||||
|
})) |
||||
|
) |
||||
|
); |
||||
|
m.data_container.removeEvents('click'); |
||||
|
|
||||
|
var plot = false; |
||||
|
if(m.info.plot && m.info.plot.length > 0) |
||||
|
plot = m.info.plot; |
||||
|
|
||||
|
// Add rating
|
||||
|
m.info_container.adopt( |
||||
|
m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', { |
||||
|
'text': parseFloat(m.info.rating.imdb[0]), |
||||
|
'title': parseInt(m.info.rating.imdb[1]) + ' votes' |
||||
|
}) : null, |
||||
|
m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', { |
||||
|
'text': m.info.genres.slice(0, 3).join(', ') |
||||
|
}) : null, |
||||
|
m.plot = plot ? new Element('span.plot', { |
||||
|
'text': plot, |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
this.toggleClass('full') |
||||
|
} |
||||
|
} |
||||
|
}) : null |
||||
|
) |
||||
|
|
||||
|
$(m).inject(c); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
$(c).inject(self.el_refresh_container, 'before'); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
self.fireEvent('loaded'); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
afterAdded: function(m, movie){ |
||||
|
var self = this; |
||||
|
|
||||
|
$(m).getElement('div.chart_number') |
||||
|
.addClass('chart_in_wanted') |
||||
|
.set('title', 'Movie in wanted list'); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
toElement: function(){ |
||||
|
return this.el; |
||||
|
} |
||||
|
|
||||
|
}) |
Loading…
Reference in new issue