diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index eafbe4071..0e3ac9c1f 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -188,3 +188,8 @@ class ShelfForm(CustomForm): class Meta: model = models.Shelf fields = ['user', 'name', 'privacy'] + +class GoalForm(CustomForm): + class Meta: + model = models.AnnualGoal + fields = ['user', 'year', 'goal', 'privacy'] diff --git a/bookwyrm/migrations/0036_annualgoal.py b/bookwyrm/migrations/0036_annualgoal.py new file mode 100644 index 000000000..fb12833ea --- /dev/null +++ b/bookwyrm/migrations/0036_annualgoal.py @@ -0,0 +1,32 @@ +# Generated by Django 3.0.7 on 2021-01-16 18:43 + +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0035_edition_edition_rank'), + ] + + operations = [ + migrations.CreateModel( + name='AnnualGoal', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('goal', models.IntegerField()), + ('year', models.IntegerField(default=2021)), + ('privacy', models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'year')}, + }, + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 48852cfe4..e71a150ba 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -17,7 +17,7 @@ from .readthrough import ReadThrough from .tag import Tag, UserTag -from .user import User, KeyPair +from .user import User, KeyPair, AnnualGoal from .relationship import UserFollows, UserFollowRequest, UserBlocks from .federated_server import FederatedServer diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index ef68f9928..6697b1b8e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -6,6 +6,7 @@ from django.apps import apps from django.contrib.auth.models import AbstractUser from django.db import models from django.dispatch import receiver +from django.utils import timezone from bookwyrm import activitypub from bookwyrm.connectors import get_data @@ -18,7 +19,7 @@ from bookwyrm.utils import regex from .base_model import OrderedCollectionPageMixin from .base_model import ActivitypubMixin, BookWyrmModel from .federated_server import FederatedServer -from . import fields +from . import fields, Review class User(OrderedCollectionPageMixin, AbstractUser): @@ -221,6 +222,57 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): return activity_object +class AnnualGoal(BookWyrmModel): + ''' set a goal for how many books you read in a year ''' + user = models.ForeignKey('User', on_delete=models.PROTECT) + goal = models.IntegerField() + year = models.IntegerField(default=timezone.now().year) + privacy = models.CharField( + max_length=255, + default='public', + choices=fields.PrivacyLevels.choices + ) + + class Meta: + ''' unqiueness constraint ''' + unique_together = ('user', 'year') + + def get_remote_id(self): + ''' put the year in the path ''' + return '%s/goal/%d' % (self.user.remote_id, self.year) + + @property + def books(self): + ''' the books you've read this year ''' + return self.user.readthrough_set.filter( + finish_date__year__gte=self.year + ).order_by('finish_date').all() + + + @property + def ratings(self): + ''' ratings for books read this year ''' + book_ids = [r.book.id for r in self.books] + reviews = Review.objects.filter( + user=self.user, + book__in=book_ids, + ) + return {r.book.id: r.rating for r in reviews} + + + @property + def progress_percent(self): + return int(float(self.book_count / self.goal) * 100) + + + @property + def book_count(self): + ''' how many books you've read this year ''' + return self.user.readthrough_set.filter( + finish_date__year__gte=self.year).count() + + + @receiver(models.signals.post_save, sender=User) #pylint: disable=unused-argument def execute_after_save(sender, instance, created, *args, **kwargs): diff --git a/bookwyrm/static/css/fonts/icomoon.eot b/bookwyrm/static/css/fonts/icomoon.eot index 30ae2cd57..48bd3f629 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.eot and b/bookwyrm/static/css/fonts/icomoon.eot differ diff --git a/bookwyrm/static/css/fonts/icomoon.svg b/bookwyrm/static/css/fonts/icomoon.svg index aa0a9e5d4..00ee337f0 100644 --- a/bookwyrm/static/css/fonts/icomoon.svg +++ b/bookwyrm/static/css/fonts/icomoon.svg @@ -7,31 +7,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bookwyrm/static/css/fonts/icomoon.ttf b/bookwyrm/static/css/fonts/icomoon.ttf index 40d6e8862..6abaa5913 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.ttf and b/bookwyrm/static/css/fonts/icomoon.ttf differ diff --git a/bookwyrm/static/css/fonts/icomoon.woff b/bookwyrm/static/css/fonts/icomoon.woff index 6cfa9a4da..2b8d33301 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.woff and b/bookwyrm/static/css/fonts/icomoon.woff differ diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index cec44f4ae..e99513b04 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -148,11 +148,11 @@ input.toggle-control:checked ~ .modal.toggle-content { position: absolute; } .quote blockquote:before { - content: "\e905"; + content: "\e906"; top: 0; left: 0; } .quote blockquote:after { - content: "\e904"; + content: "\e905"; right: 0; } diff --git a/bookwyrm/static/css/icons.css b/bookwyrm/static/css/icons.css index 536db5600..8f1f4e903 100644 --- a/bookwyrm/static/css/icons.css +++ b/bookwyrm/static/css/icons.css @@ -1,10 +1,10 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?rd4abb'); - src: url('fonts/icomoon.eot?rd4abb#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?rd4abb') format('truetype'), - url('fonts/icomoon.woff?rd4abb') format('woff'), - url('fonts/icomoon.svg?rd4abb#icomoon') format('svg'); + src: url('fonts/icomoon.eot?uh765c'); + src: url('fonts/icomoon.eot?uh765c#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?uh765c') format('truetype'), + url('fonts/icomoon.woff?uh765c') format('woff'), + url('fonts/icomoon.svg?uh765c#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; @@ -25,81 +25,102 @@ -moz-osx-font-smoothing: grayscale; } -.icon-dots-three-vertical:before { - content: "\e918"; +.icon-sparkle:before { + content: "\e91a"; } -.icon-check:before { - content: "\e917"; +.icon-warning:before { + content: "\e91b"; } -.icon-dots-three:before { - content: "\e916"; -} -.icon-envelope:before { +.icon-book:before { content: "\e900"; } -.icon-arrow-right:before { +.icon-bookmark:before { + content: "\e91c"; +} +.icon-envelope:before { content: "\e901"; } -.icon-bell:before { +.icon-arrow-right:before { content: "\e902"; } -.icon-x:before { +.icon-bell:before { content: "\e903"; } -.icon-quote-close:before { +.icon-x:before { content: "\e904"; } -.icon-quote-open:before { +.icon-quote-close:before { content: "\e905"; } -.icon-image:before { +.icon-quote-open:before { content: "\e906"; } -.icon-pencil:before { +.icon-image:before { content: "\e907"; } -.icon-list:before { +.icon-pencil:before { content: "\e908"; } -.icon-unlock:before { +.icon-list:before { content: "\e909"; } -.icon-globe:before { +.icon-unlock:before { content: "\e90a"; } -.icon-lock:before { +.icon-unlisted:before { + content: "\e90a"; +} +.icon-globe:before { content: "\e90b"; } -.icon-chain-broken:before { +.icon-public:before { + content: "\e90b"; +} +.icon-lock:before { content: "\e90c"; } -.icon-chain:before { +.icon-followers:before { + content: "\e90c"; +} +.icon-chain-broken:before { content: "\e90d"; } -.icon-comments:before { +.icon-chain:before { content: "\e90e"; } -.icon-comment:before { +.icon-comments:before { content: "\e90f"; } -.icon-boost:before { +.icon-comment:before { content: "\e910"; } -.icon-arrow-left:before { +.icon-boost:before { content: "\e911"; } -.icon-arrow-up:before { +.icon-arrow-left:before { content: "\e912"; } -.icon-arrow-down:before { +.icon-arrow-up:before { content: "\e913"; } -.icon-home:before { +.icon-arrow-down:before { content: "\e914"; } -.icon-local:before { +.icon-home:before { content: "\e915"; } +.icon-local:before { + content: "\e916"; +} +.icon-dots-three:before { + content: "\e917"; +} +.icon-check:before { + content: "\e918"; +} +.icon-dots-three-vertical:before { + content: "\e919"; +} .icon-search:before { content: "\e986"; } diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index de6d44f99..b2de57368 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -21,11 +21,38 @@ window.onload = function() { // handle aria settings on menus Array.from(document.getElementsByClassName('pulldown-menu')) .forEach(t => t.onclick = toggleMenu); + + // display based on localstorage vars + document.querySelectorAll('[data-hide]') + .forEach(t => setDisplay(t)); + + // update localstorage + Array.from(document.getElementsByClassName('set-display')) + .forEach(t => t.onclick = updateDisplay); }; +function updateDisplay(e) { + var key = e.target.getAttribute('data-id'); + var value = e.target.getAttribute('data-value'); + window.localStorage.setItem(key, value); + + document.querySelectorAll('[data-hide="' + key + '"]') + .forEach(t => setDisplay(t)); +} + +function setDisplay(el) { + var key = el.getAttribute('data-hide'); + var value = window.localStorage.getItem(key) + if (!value) { + el.className = el.className.replace('hidden', ''); + } else if (value != null && !!value) { + el.className += ' hidden'; + } +} + function toggleAction(e) { // set hover, if appropriate - var hover = e.target.getAttribute('data-hover-target') + var hover = e.target.getAttribute('data-hover-target'); if (hover) { document.getElementById(hover).focus(); } diff --git a/bookwyrm/templates/feed.html b/bookwyrm/templates/feed.html index b77da819b..79dd4b85e 100644 --- a/bookwyrm/templates/feed.html +++ b/bookwyrm/templates/feed.html @@ -47,8 +47,8 @@

- {% include 'snippets/book_titleby.html' with book=book %} - + {% include 'snippets/book_titleby.html' with book=book %} +

{% include 'snippets/toggle/toggle_button.html' with label="close" controls_text="no-book" class="delete" %}
@@ -67,6 +67,15 @@
{% endif %} + + {% if goal %} +
+
+

{{ goal.year }} Reading Goal

+ {% include 'snippets/goal_progress.html' with goal=goal %} +
+
+ {% endif %}
@@ -85,6 +94,33 @@
+ {# announcements and system messages #} + {% if not goal and tab == 'home' %} + {% now 'Y' as year %} + + {% endif %} + + {# activity feed #} {% if not activities %}

There aren't any activities right now! Try following a user to get started

{% endif %} diff --git a/bookwyrm/templates/goal.html b/bookwyrm/templates/goal.html new file mode 100644 index 000000000..4f477a216 --- /dev/null +++ b/bookwyrm/templates/goal.html @@ -0,0 +1,60 @@ +{% extends 'layout.html' %} +{% block content %} + +
+

{{ year }} Reading Progress

+ {% if user == request.user %} +
+ {% if goal %} + + + {% endif %} +
+
+ +
+ {% now 'Y' as year %} +
+
+

+ {{ year }} reading goal +

+
+
+

Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.

+ + {% include 'snippets/goal_form.html' with goal=goal year=year %} +
+
+
+
+ {% endif %} + + {% if not goal and user != request.user %} +

{{ user.display_name }} hasn't set a reading goal for {{ year }}.

+ {% endif %} + + {% if goal %} + {% include 'snippets/goal_progress.html' with goal=goal %} + {% endif %} +
+ +{% if goal.books %} +
+

{% if goal.user == request.user %}Your{% else %}{{ goal.user.display_name }}'s{% endif %} {{ year }} Books

+ +
+{% endif %} +{% endblock %} diff --git a/bookwyrm/templates/snippets/discover/small-book.html b/bookwyrm/templates/snippets/discover/small-book.html index 76fd2db78..be399df66 100644 --- a/bookwyrm/templates/snippets/discover/small-book.html +++ b/bookwyrm/templates/snippets/discover/small-book.html @@ -1,7 +1,9 @@ {% load bookwyrm_tags %} {% if book %} {% include 'snippets/book_cover.html' with book=book %} +{% if ratings %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %} +{% endif %}

{{ book.title }}

{% if book.authors %} diff --git a/bookwyrm/templates/snippets/finish_reading_modal.html b/bookwyrm/templates/snippets/finish_reading_modal.html index 06874c069..79bcd9449 100644 --- a/bookwyrm/templates/snippets/finish_reading_modal.html +++ b/bookwyrm/templates/snippets/finish_reading_modal.html @@ -29,8 +29,8 @@