diff --git a/cps/static/css/caliBlur_override.css b/cps/static/css/caliBlur_override.css new file mode 100644 index 00000000..05a7c0d8 --- /dev/null +++ b/cps/static/css/caliBlur_override.css @@ -0,0 +1,17 @@ +body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{ + display: none; +} + +.cover .badge{ + position: absolute; + top: 0; + right: 0; + background-color: #cc7b19; + border-radius: 0; + padding: 0 8px; + box-shadow: 0 0 4px rgba(0,0,0,.6); + line-height: 24px; +} +.cover{ + box-shadow: 0 0 4px rgba(0,0,0,.6); +} diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 0850e10a..1f4eeaa6 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -64,6 +64,12 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te .navbar-default .navbar-toggle .icon-bar {background-color: #000;} .navbar-default .navbar-toggle {border-color: #000;} .cover { margin-bottom: 10px;} +.cover .badge{ + position: absolute; + top: 10px; + right: 10px; + background-color: #45b29d; +} .cover-height { max-height: 100px;} .col-sm-2 a .cover-small { margin:5px; diff --git a/cps/static/js/filter_grid.js b/cps/static/js/filter_grid.js new file mode 100644 index 00000000..0e63cea5 --- /dev/null +++ b/cps/static/js/filter_grid.js @@ -0,0 +1,62 @@ +/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) + * Copyright (C) 2018 OzzieIsaacs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var $list = $("#list").isotope({ + itemSelector: ".book", + layoutMode: "fitRows", + getSortData: { + title: ".title", + } +}); + +$("#desc").click(function() { + $list.isotope({ + sortBy: "name", + sortAscending: false + }); + return; +}); + +$("#asc").click(function() { + $list.isotope({ + sortBy: "name", + sortAscending: true + }); + return; +}); + +$("#all").click(function() { + // go through all elements and make them visible + $(".sortable").each(function() { + $(this).show(); + }); + // We need to trigger the resize event to have all the grid item to re-align. + window.dispatchEvent(new Event('resize')); +}); + +$(".char").click(function() { + var character = this.innerText; + $(".sortable").each(function() { + if (this.attributes["data-id"].value.charAt(0).toUpperCase() !== character) { + $(this).hide(); + } else { + $(this).show(); + } + }); + // We need to trigger the resize event to have all the grid item to re-align. + window.dispatchEvent(new Event("resize")); +}); diff --git a/cps/static/js/main.js b/cps/static/js/main.js index bd8d04f9..ad95d724 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -281,4 +281,17 @@ $(function() { $(".discover .row").isotope("layout"); }); + $(".update-view").click(function(e) { + var target = $(this).data("target"); + var view = $(this).data("view"); + + e.preventDefault(); + e.stopPropagation(); + var data = {}; + data[target] = view; + console.debug("Updating view data: ", data); + $.post( "/ajax/view", data).done(function( ) { + location.reload(); + }); + }); }); diff --git a/cps/templates/grid.html b/cps/templates/grid.html new file mode 100644 index 00000000..613e9fe4 --- /dev/null +++ b/cps/templates/grid.html @@ -0,0 +1,54 @@ +{% extends "layout.html" %} +{% block body %} +
+

{{_(title)}}

+ + + + {% if entries[0] %} +
+ {% for entry in entries %} + + {% endfor %} +
+ {% endif %} + + +{% endblock %} +{% block js %} + +{% endblock %} diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 559eeabc..fd7c3174 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -17,6 +17,7 @@ {% if g.current_theme == 1 %} + {% endif %} @@ -25,7 +26,7 @@ - +
diff --git a/cps/ub.py b/cps/ub.py index 21dc6e3c..c90d4c9d 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -199,6 +199,7 @@ class User(UserBase, Base): denied_column_value = Column(String, default="") allowed_column_value = Column(String, default="") remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic') + series_view = Column(String(10), default="list") if oauth_support: @@ -238,6 +239,7 @@ class Anonymous(AnonymousUserMixin, UserBase): self.allowed_tags = data.allowed_tags self.denied_column_value = data.denied_column_value self.allowed_column_value = data.allowed_column_value + self.series_view = data.series_view def role_admin(self): return False @@ -344,7 +346,7 @@ class RemoteAuthToken(Base): # Migrate database to current version, has to be updated after every database change. Currently migration from -# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding +# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding # rows with SQL commands def migrate_Database(session): engine = session.bind @@ -423,6 +425,12 @@ def migrate_Database(session): conn.execute("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''") conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''") conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''") + try: + session.query(exists().where(User.series_view)).scalar() + except exc.OperationalError: + conn = engine.connect() + conn.execute("ALTER TABLE user ADD column `series_view` VARCHAR(10) DEFAULT 'list'") + if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None: create_anonymous_user(session) try: @@ -442,11 +450,12 @@ def migrate_Database(session): "sidebar_view INTEGER," "default_language VARCHAR(3)," "mature_content BOOLEAN," + "series_view VARCHAR(10)," "UNIQUE (nickname)," "UNIQUE (email)," "CHECK (mature_content IN (0, 1)))") conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale," - "sidebar_view, default_language, mature_content) " + "sidebar_view, default_language, mature_content, series_view) " "SELECT id, nickname, email, role, password, kindle_mail, locale," "sidebar_view, default_language, mature_content FROM user") # delete old user table and rename new user_id table to user: @@ -483,7 +492,7 @@ def delete_download(book_id): session.query(Downloads).filter(book_id == Downloads.book_id).delete() session.commit() -# Generate user Guest (translated text), as anoymous user, no rights +# Generate user Guest (translated text), as anonymous user, no rights def create_anonymous_user(session): user = User() user.nickname = "Guest" diff --git a/cps/web.py b/cps/web.py index 81246172..85b6f71d 100644 --- a/cps/web.py +++ b/cps/web.py @@ -36,7 +36,7 @@ from flask import Blueprint from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for from flask_babel import gettext as _ from flask_login import login_user, logout_user, login_required, current_user -from sqlalchemy.exc import IntegrityError +from sqlalchemy.exc import IntegrityError, InvalidRequestError from sqlalchemy.sql.expression import text, func, true, false, not_, and_, exists, or_ from werkzeug.exceptions import default_exceptions from werkzeug.datastructures import Headers @@ -341,6 +341,25 @@ def toggle_read(book_id): return "" +@web.route("/ajax/view", methods=["POST"]) +@login_required +def update_view(): + to_save = request.form.to_dict() + allowed_view = ['grid', 'list'] + if "series_view" in to_save and to_save["series_view"] in allowed_view: + current_user.series_view = to_save["series_view"] + else: + log.error("Invalid request received: %r %r", request, to_save) + return "Invalid request", 400 + + try: + ub.session.commit() + except InvalidRequestError: + log.error("Invalid request received: %r ", request, ) + return "Invalid request", 400 + return "", 200 + + ''' @web.route("/ajax/getcomic///") @login_required @@ -705,14 +724,50 @@ def publisher_list(): @login_required_if_no_ano def series_list(): if current_user.check_visibility(constants.SIDEBAR_SERIES): - entries = db.session.query(db.Series, func.count('books_series_link.book').label('count'))\ - .join(db.books_series_link).join(db.Books).filter(common_filters())\ - .group_by(text('books_series_link.series')).order_by(db.Series.sort).all() - charlist = db.session.query(func.upper(func.substr(db.Series.sort,1,1)).label('char')) \ + charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \ .join(db.books_series_link).join(db.Books).filter(common_filters()) \ - .group_by(func.upper(func.substr(db.Series.sort,1,1))).all() - return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, - title=_(u"Series"), page="serieslist", data="series") + .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() + + if current_user.series_view == 'list': + entries = db.session.query(db.Series, func.count('books_series_link.book').label('count'))\ + .join(db.books_series_link).join(db.Books).filter(common_filters())\ + .group_by(text('books_series_link.series')).order_by(db.Series.sort).all() + return render_title_template( + 'list.html', + entries=entries, + folder='web.books_list', + charlist=charlist, + title=_(u"Series list"), + page="serieslist", + data="series", + bodyClass="list-view" + ) + else: + entries = db.session.query( + db.Books, + func.count('books_series_link').label('count') + ).join( + db.books_series_link + ).join( + db.Series + ).filter( + common_filters() + ).group_by( + text('books_series_link.series') + ).order_by( + db.Series.sort + ).all() + + return render_title_template( + 'grid.html', + entries=entries, + folder='web.books_list', + charlist=charlist, + title=_(u"Series list"), + page="serieslist", + data="series", + bodyClass="grid-view" + ) else: abort(404)