Initial simple implementation of category view in new content server frontend

This commit is contained in:
Kovid Goyal 2010-10-13 12:54:20 -06:00
parent 404a5080a9
commit 2a8c2661a6
8 changed files with 244 additions and 40 deletions

View File

@ -82,9 +82,9 @@ body {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
text-shadow: #27211b 1px 1px 1px;
-moz-box-shadow: 5px 5px 5px #222;
-webkit-box-shadow: 5px 5px 5px #222;
box-shadow: 5px 5px 5px #222;
-moz-box-shadow: 5px 5px 5px #222;
-webkit-box-shadow: 5px 5px 5px #222;
box-shadow: 5px 5px 5px #222;
}
@ -220,3 +220,32 @@ h2.library_name {
/* }}} */
/* Category {{{ */
.category ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.category li.category-item {
margin: 0.75em;
padding: 0.75em;
text-align: center;
cursor: pointer;
}
.category li.category-item:hover {
background-color: #d6d3c9;
font-weight: bold;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
}
.category li.category-item h4 { display: inline }
.category li.category-item span.href { display: none }
/* }}} */

View File

@ -15,7 +15,10 @@
<script type="text/javascript" src="/static/jquery.js"></script>
<script type="text/javascript" src="/static/jquery.corner.js"></script>
<script type="text/javascript" src="/static/jquery_ui/js/jquery-ui-1.8.5.custom.min.js"></script>
<script type="text/javascript"
src="/static/jquery_ui/js/jquery-ui-1.8.5.custom.min.js"></script>
<script type="text/javascript" src="/static/browse/browse.js"></script>

View File

@ -127,3 +127,16 @@ function toplevel() {
});
}
// }}}
// Category feed {{{
function category() {
$(".category li").corner("15px");
$(".category li").click(function() {
var href = $(this).children("span.href").html();
window.location = href;
});
}
// }}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

View File

@ -5,12 +5,122 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import operator, os
import operator, os, sys
from urllib import quote
from binascii import hexlify
import cherrypy
from calibre.constants import filesystem_encoding
from calibre import isbytestring, force_unicode, prepare_string_for_xml as xml
from calibre.library.server.utils import Offsets
def paginate(offsets, content, base_url, up_url=None):
'Create markup for pagination'
if '?' not in base_url:
base_url += '?'
if base_url[-1] != '?':
base_url += '&'
def navlink(decoration, name, cls, offset):
label = xml(name)
if cls in ('next', 'last'):
label += '&nbsp;' + decoration
else:
label = decoration + '&nbsp;' + label
return (u'<a class="{cls}" href="{base_url}&amp;offset={offset}" title={name}>'
u'{label}</a>').format(cls=cls, decoration=decoration,
name=xml(name, True), offset=offset,
base_url=xml(base_url, True), label=label)
left = ''
if offsets.offset > 0 and offsets.previous_offset > 0:
left += navlink(u'\u219e', _('First'), 'first', 0)
if offsets.offset > 0:
left += ' ' + navlink('&larr;', _('Previous'), 'previous',
offsets.previous_offset)
middle = ''
if up_url:
middle = '<a href="{0}" title="{1}">[{1} &uarr;]</a>'.format(xml(up_url, True),
xml(_('Up')))
right = ''
if offsets.next_offset > -1:
right += navlink('&rarr', _('Next'), 'next', offsets.next_offset)
if offsets.last_offset > offsets.next_offset and offsets.last_offset > 0:
right += ' ' + navlink(u'\u21A0', _('Last'), 'last', offsets.last_offset)
navbar = u'''
<table class="navbar">
<tr>
<td class="left">{left}</td>
<td class="middle">{middle}</td>
<td class="right">{right}</td>
</tr>
<table>
'''.format(left=left, right=right, middle=middle)
templ = u'''
<div class="page">
{navbar}
<div class="page-contents">
{content}
</div>
{navbar}
</div>
'''
return templ.format(navbar=navbar, content=content)
def utf8(x):
if isinstance(x, unicode):
x = x.encode('utf-8')
return x
def render_rating(rating, container='span'):
if rating < 0.1:
return '', ''
added = 0
rstring = xml(_('Average rating: %.1f stars')% (rating if rating else 0.0),
True)
ans = ['<%s class="rating">' % (container)]
for i in range(5):
n = rating - added
x = 'half'
if n <= 0.1:
x = 'off'
elif n >= 0.9:
x = 'on'
ans.append(
u'<img alt="{0}" title="{0}" src="/static/star-{1}.png" />'.format(
rstring, x))
added += 1
ans.append('</%s>'%container)
return u''.join(ans), rstring
def get_category_items(category, items, offsets, db):
def item(i):
templ = (u'<li title="{4}" class="category-item">'
'<h4>{0}&nbsp;&nbsp;{1}</h4>&nbsp;&nbsp;{2}'
'<span class="href">{3}</span></li>')
rating, rstring = render_rating(i.avg_rating)
name = xml(i.name)
id_ = i.id
if id_ is None:
id_ = hexlify(force_unicode(name).encode('utf-8'))
id_ = xml(str(id_))
desc = ''
if i.count > 0:
desc += '[' + _('%d items')%i.count + ']'
href = '/browse/matches/%s/%s'%(category, id_)
return templ.format(xml(name), rating,
xml(desc), xml(quote(href)), rstring)
items = list(map(item, items[offsets.offset:offsets.slice_upper_bound]))
return '\n'.join(['<ul>'] + items + ['</ul>'])
class BrowseServer(object):
@ -23,7 +133,6 @@ class BrowseServer(object):
connect('browse_search', base_href+'/search/{query}',
self.browse_search)
connect('browse_book', base_href+'/book/{uuid}', self.browse_book)
connect('browse_json', base_href+'/json/{query}', self.browse_json)
def browse_template(self, category=True):
@ -36,7 +145,8 @@ class BrowseServer(object):
sort_opts = [(x, fm[x]['name']) for x in fm.sortable_field_keys()
if fm[x]['name']]
prefix = 'category' if category else 'book'
ans = P('content_server/browse/browse.html', data=True)
ans = P('content_server/browse/browse.html',
data=True).decode('utf-8')
ans = ans.replace('{sort_select_label}', xml(_('Sort by')+':'))
opts = ['<option value="%s_%s">%s</option>' % (prefix, xml(k),
xml(n)) for k, n in
@ -61,50 +171,102 @@ class BrowseServer(object):
# Catalogs {{{
def browse_catalog(self, category=None):
if category == None:
categories = self.categories_cache()
category_meta = self.db.field_metadata
cats = [
(_('Newest'), 'newest'),
]
def getter(x):
return category_meta[x]['name'].lower()
for category in sorted(categories,
cmp=lambda x,y: cmp(getter(x), getter(y))):
if len(categories[category]) == 0:
continue
if category == 'formats':
continue
meta = category_meta.get(category, None)
if meta is None:
continue
cats.append((meta['name'], category))
cats = ['<li title="{2} {0}">{0}<span>/browse/category/{1}</span></li>'.format(xml(x, True),
xml(y), xml(_('Browse books by'))) for x, y in cats]
def browse_toplevel(self):
categories = self.categories_cache()
category_meta = self.db.field_metadata
cats = [
(_('Newest'), 'newest'),
]
main = '<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\
.format(_('Choose a category to browse by:'), '\n\n'.join(cats))
ans = self.browse_template().format(title='',
script='toplevel();', main=main)
else:
def getter(x):
return category_meta[x]['name'].lower()
for category in sorted(categories,
cmp=lambda x,y: cmp(getter(x), getter(y))):
if len(categories[category]) == 0:
continue
if category == 'formats':
continue
meta = category_meta.get(category, None)
if meta is None:
continue
cats.append((meta['name'], category))
cats = ['<li title="{2} {0}">{0}<span>/browse/category/{1}</span></li>'\
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')))
for x, y in cats]
main = '<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\
.format(_('Choose a category to browse by:'), '\n\n'.join(cats))
return self.browse_template().format(title='',
script='toplevel();', main=main)
def browse_category(self, category, offset, sort, subcategory=None):
categories = self.categories_cache()
category_meta = self.db.field_metadata
category_name = category_meta[category]['name']
if category not in categories:
raise cherrypy.HTTPError(404, 'category not found')
items = categories[category]
base_url='/browse/category/'+category+'?'
if subcategory is not None:
base_url += 'subcategory='+quote(subcategory)
if sort is not None:
base_url += 'sort='+quote(sort)
script = 'category();'
max_items = sys.maxint
offsets = Offsets(offset, max_items, len(items))
items = list(items)[offsets.offset:offsets.offset+max_items]
items = get_category_items(category, items, offsets, self.db)
main = u'''
<div class="category">
<h3>{0}</h3>
<p><a class="navlink" href="/browse" title="{2}"
>[{2}&nbsp;&uarr;]</a>
</p>
{1}
</div>
'''.format(
xml(_('Browsing by')+': ' + category_name), items,
xml(_('Up'), True))
return self.browse_template().format(title=category_name,
script=script, main=main)
def browse_catalog(self, category=None, offset=0, sort=None,
subcategory=None):
'Entry point for top-level, categories and sub-categories'
try:
offset = int(offset)
except:
raise cherrypy.HTTPError(404, 'Not found')
if category == None:
ans = self.browse_toplevel()
else:
ans = self.browse_category(category, offset, sort)
cherrypy.response.headers['Content-Type'] = 'text/html'
updated = self.db.last_modified()
cherrypy.response.headers['Last-Modified'] = \
self.last_modified(max(updated, self.build_time))
return ans
return utf8(ans)
# }}}
# Book Lists {{{
def browse_list(self, query=None):
def browse_list(self, query=None, offset=0, sort=None):
raise NotImplementedError()
# }}}
# Search {{{
def browse_search(self, query=None):
def browse_search(self, query=None, offset=0, sort=None):
raise NotImplementedError()
# }}}
@ -113,8 +275,4 @@ class BrowseServer(object):
raise NotImplementedError()
# }}}
# JSON {{{
def browse_json(self, query=None):
raise NotImplementedError()
# }}}

View File

@ -23,6 +23,7 @@ class Offsets(object):
raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset)
last_allowed_index = total - 1
last_current_index = offset + delta - 1
self.slice_upper_bound = offset+delta
self.offset = offset
self.next_offset = last_current_index + 1
if self.next_offset > last_allowed_index: