diff --git a/client/src/components/book-navigation/ContentNavigation.vue b/client/src/components/book-navigation/ContentNavigation.vue
index ed4c327b..7dbe4ae2 100644
--- a/client/src/components/book-navigation/ContentNavigation.vue
+++ b/client/src/components/book-navigation/ContentNavigation.vue
@@ -6,7 +6,7 @@
Themen
@@ -73,6 +73,7 @@
import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation';
import sidebarMixin from '@/mixins/sidebar';
+ import meMixin from '@/mixins/me';
export default {
props: {
@@ -81,13 +82,27 @@
}
},
- mixins: [sidebarMixin],
+ mixins: [sidebarMixin, meMixin],
components: {
BookTopicNavigation,
Logo
},
+ computed: {
+ topicRoute() {
+ if (this.me.lastTopic && this.me.lastTopic.slug) {
+ return {
+ name: 'topic',
+ params: {
+ topicSlug: this.me.lastTopic.slug
+ }
+ }
+ }
+ return '/book/topic/berufliche-grundbildung'
+ }
+ },
+
methods: {
isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1;
diff --git a/client/src/graphql/gql/fragments/topicParts.gql b/client/src/graphql/gql/fragments/topicParts.gql
new file mode 100644
index 00000000..3edc4716
--- /dev/null
+++ b/client/src/graphql/gql/fragments/topicParts.gql
@@ -0,0 +1,17 @@
+#import "./moduleParts.gql"
+fragment TopicParts on TopicNode {
+ id
+ title
+ teaser
+ slug
+ description
+ vimeoId
+ instructions
+ modules {
+ edges {
+ node {
+ ...ModuleParts
+ }
+ }
+ }
+}
diff --git a/client/src/graphql/gql/fragments/userParts.gql b/client/src/graphql/gql/fragments/userParts.gql
index 4724b6dd..6a57ae18 100644
--- a/client/src/graphql/gql/fragments/userParts.gql
+++ b/client/src/graphql/gql/fragments/userParts.gql
@@ -12,6 +12,10 @@ fragment UserParts on UserNode {
id
slug
}
+ lastTopic {
+ id
+ slug
+ }
selectedClass {
id
}
diff --git a/client/src/graphql/gql/mutations/updateLastTopic.gql b/client/src/graphql/gql/mutations/updateLastTopic.gql
new file mode 100644
index 00000000..8bb52075
--- /dev/null
+++ b/client/src/graphql/gql/mutations/updateLastTopic.gql
@@ -0,0 +1,8 @@
+#import "../fragments/topicParts.gql"
+mutation UpdateLastTopic($input: UpdateLastTopicInput!) {
+ updateLastTopic(input: $input) {
+ topic {
+ ...TopicParts
+ }
+ }
+}
diff --git a/client/src/graphql/gql/topicQuery.gql b/client/src/graphql/gql/topicQuery.gql
index a477166a..64226fe0 100644
--- a/client/src/graphql/gql/topicQuery.gql
+++ b/client/src/graphql/gql/topicQuery.gql
@@ -1,18 +1,6 @@
-#import "./fragments/moduleParts.gql"
+#import "./fragments/topicParts.gql"
query Topic($slug: String!){
topic(slug: $slug) {
- id
- title
- teaser
- description
- vimeoId
- instructions
- modules {
- edges {
- node {
- ...ModuleParts
- }
- }
- }
+ ...TopicParts
}
}
diff --git a/client/src/pages/start.vue b/client/src/pages/start.vue
index 328d1ce7..47098720 100644
--- a/client/src/pages/start.vue
+++ b/client/src/pages/start.vue
@@ -63,10 +63,13 @@
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
- import {meQuery} from '@/graphql/queries';
import MobileHeader from '@/components/MobileHeader';
+ import meMixin from '@/mixins/me';
+
export default {
+
+ mixins: [meMixin],
components: {
MobileHeader,
HeaderBar,
@@ -77,12 +80,6 @@
RoomsIllustration
},
- data() {
- return {
- me: {}
- }
- },
-
computed: {
moduleRoute() {
if (this.me.lastModule && this.me.lastModule.slug) {
@@ -98,9 +95,6 @@
}
},
- apollo: {
- me: meQuery
- },
}
diff --git a/client/src/pages/topic.vue b/client/src/pages/topic.vue
index 20130572..12fcc509 100644
--- a/client/src/pages/topic.vue
+++ b/client/src/pages/topic.vue
@@ -45,6 +45,9 @@
import me from '@/mixins/me';
import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation';
+ import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql';
+ import ME_QUERY from '@/graphql/gql/meQuery.gql';
+
export default {
mixins: [me],
@@ -64,6 +67,12 @@
},
update(data) {
return this.$getRidOfEdges(data).topic || {};
+ },
+ result(r, key) {
+ if (this.saveMe) {
+ this.saveMe = false;
+ this.updateLastVisitedTopic(this.topic.id);
+ }
}
}
}
@@ -75,7 +84,8 @@
modules: {
edges: []
}
- }
+ },
+ saveMe: false
}
},
@@ -85,9 +95,37 @@
}
},
+ mounted() {
+ if (!this.topic.id) { // component was loaded before topic, apollo not ready yet
+ this.saveMe = true; // needs saving, apollo will do this
+ } else {
+ this.updateLastVisitedTopic(this.topic.id);
+ }
+ },
+
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
+ },
+ updateLastVisitedTopic(topicId) {
+ this.$apollo.mutate({
+ mutation: UPDATE_LAST_TOPIC_MUTATION,
+ variables: {
+ input: {
+ id: topicId
+ }
+ },
+ update(store, {data: {updateLastTopic: {topic}}}) {
+ if (topic) {
+ let query = ME_QUERY;
+ const data = store.readQuery({query});
+ if (data) {
+ data.me.lastTopic = topic;
+ store.writeQuery({query, data})
+ }
+ }
+ }
+ });
}
},
}
diff --git a/server/books/schema/mutations/main.py b/server/books/schema/mutations/main.py
index 5d2bb6f4..075f8cf8 100644
--- a/server/books/schema/mutations/main.py
+++ b/server/books/schema/mutations/main.py
@@ -1,5 +1,5 @@
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
-from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule
+from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, UpdateLastTopic
class BookMutations(object):
@@ -8,3 +8,4 @@ class BookMutations(object):
delete_content_block = DeleteContentBlock.Field()
update_solution_visibility = UpdateSolutionVisibility.Field()
update_last_module = UpdateLastModule.Field()
+ update_last_topic = UpdateLastTopic.Field()
diff --git a/server/books/schema/mutations/module.py b/server/books/schema/mutations/module.py
index 1165f2d6..98b8f765 100644
--- a/server/books/schema/mutations/module.py
+++ b/server/books/schema/mutations/module.py
@@ -2,8 +2,8 @@ import graphene
from graphene import relay
from api.utils import get_errors, get_object
-from books.models import Module
-from books.schema.queries import ModuleNode
+from books.models import Module, Topic
+from books.schema.queries import ModuleNode, TopicNode
class UpdateSolutionVisibility(relay.ClientIDMutation):
@@ -71,3 +71,26 @@ class UpdateLastModule(relay.ClientIDMutation):
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(errors=errors)
+
+
+class UpdateLastTopic(relay.ClientIDMutation):
+ class Input:
+ # todo: use slug here too
+ id = graphene.ID()
+
+ topic = graphene.Field(TopicNode)
+
+ @classmethod
+ def mutate_and_get_payload(cls, root, info, **args):
+ user = info.context.user
+ id = args.get('id')
+
+ topic = get_object(Topic, id)
+ if not topic:
+ raise Topic.DoesNotExist
+
+ user.last_topic = topic
+ user.save()
+
+ return cls(topic=topic)
+
diff --git a/server/users/migrations/0018_user_last_topic.py b/server/users/migrations/0018_user_last_topic.py
new file mode 100644
index 00000000..f2da46b8
--- /dev/null
+++ b/server/users/migrations/0018_user_last_topic.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.1.15 on 2020-06-15 14:24
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('books', '0021_auto_20200525_1447'),
+ ('users', '0017_auto_20200430_1251'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='last_topic',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='books.Topic'),
+ ),
+ ]
diff --git a/server/users/models.py b/server/users/models.py
index 99fd867b..a551808f 100644
--- a/server/users/models.py
+++ b/server/users/models.py
@@ -17,6 +17,7 @@ DEFAULT_SCHOOL_ID = 1
class User(AbstractUser):
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
+ last_topic = models.ForeignKey('books.Topic', related_name='+', on_delete=models.SET_NULL, null=True)
avatar_url = models.CharField(max_length=254, blank=True, default='')
email = models.EmailField(_('email address'), unique=True)
hep_id = models.PositiveIntegerField(null=True, blank=False)
diff --git a/server/users/schema.py b/server/users/schema.py
index b23434d9..d3bab44c 100644
--- a/server/users/schema.py
+++ b/server/users/schema.py
@@ -50,7 +50,8 @@ class UserNode(DjangoObjectType):
class Meta:
model = User
filter_fields = ['username', 'email']
- only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module', 'avatar_url',
+ only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
+ 'last_topic', 'avatar_url',
'selected_class', 'expiry_date']
interfaces = (relay.Node,)
@@ -64,7 +65,7 @@ class UserNode(DjangoObjectType):
return self.selected_class()
def resolve_expiry_date(self, info):
- if not self.hep_id: # concerns users that already have an (old) account
+ if not self.hep_id: # concerns users that already have an (old) account
return format(datetime(2020, 7, 31), 'U') # just set some expiry date
else:
return self.license_expiry_date