Save last topic and go there on topic navigation

This commit is contained in:
Ramon Wenger 2020-06-16 16:29:41 +02:00
parent a9b3dddd8b
commit c6bdae3fd3
12 changed files with 142 additions and 32 deletions

View File

@ -6,7 +6,7 @@
<div class="content-navigation__item"> <div class="content-navigation__item">
<router-link <router-link
:class="{'content-navigation__link--active': isActive('book')}" :class="{'content-navigation__link--active': isActive('book')}"
to="/book/topic/berufliche-grundbildung" :to="topicRoute"
active-class="content-navigation__link--active" active-class="content-navigation__link--active"
class="content-navigation__link" class="content-navigation__link"
@click.native="close">Themen @click.native="close">Themen
@ -73,6 +73,7 @@
import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation'; import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation';
import sidebarMixin from '@/mixins/sidebar'; import sidebarMixin from '@/mixins/sidebar';
import meMixin from '@/mixins/me';
export default { export default {
props: { props: {
@ -81,13 +82,27 @@
} }
}, },
mixins: [sidebarMixin], mixins: [sidebarMixin, meMixin],
components: { components: {
BookTopicNavigation, BookTopicNavigation,
Logo 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: { methods: {
isActive(linkName) { isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1; return linkName === 'book' && this.$route.path.indexOf('module') > -1;

View File

@ -0,0 +1,17 @@
#import "./moduleParts.gql"
fragment TopicParts on TopicNode {
id
title
teaser
slug
description
vimeoId
instructions
modules {
edges {
node {
...ModuleParts
}
}
}
}

View File

@ -12,6 +12,10 @@ fragment UserParts on UserNode {
id id
slug slug
} }
lastTopic {
id
slug
}
selectedClass { selectedClass {
id id
} }

View File

@ -0,0 +1,8 @@
#import "../fragments/topicParts.gql"
mutation UpdateLastTopic($input: UpdateLastTopicInput!) {
updateLastTopic(input: $input) {
topic {
...TopicParts
}
}
}

View File

@ -1,18 +1,6 @@
#import "./fragments/moduleParts.gql" #import "./fragments/topicParts.gql"
query Topic($slug: String!){ query Topic($slug: String!){
topic(slug: $slug) { topic(slug: $slug) {
id ...TopicParts
title
teaser
description
vimeoId
instructions
modules {
edges {
node {
...ModuleParts
}
}
}
} }
} }

View File

@ -63,10 +63,13 @@
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration'; import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
import RoomsIllustration from '@/components/illustrations/RoomsIllustration'; import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
import {meQuery} from '@/graphql/queries';
import MobileHeader from '@/components/MobileHeader'; import MobileHeader from '@/components/MobileHeader';
import meMixin from '@/mixins/me';
export default { export default {
mixins: [meMixin],
components: { components: {
MobileHeader, MobileHeader,
HeaderBar, HeaderBar,
@ -77,12 +80,6 @@
RoomsIllustration RoomsIllustration
}, },
data() {
return {
me: {}
}
},
computed: { computed: {
moduleRoute() { moduleRoute() {
if (this.me.lastModule && this.me.lastModule.slug) { if (this.me.lastModule && this.me.lastModule.slug) {
@ -98,9 +95,6 @@
} }
}, },
apollo: {
me: meQuery
},
} }
</script> </script>

View File

@ -45,6 +45,9 @@
import me from '@/mixins/me'; import me from '@/mixins/me';
import BookTopicNavigation from '@/components/book-navigation/BookTopicNavigation'; 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 { export default {
mixins: [me], mixins: [me],
@ -64,6 +67,12 @@
}, },
update(data) { update(data) {
return this.$getRidOfEdges(data).topic || {}; return this.$getRidOfEdges(data).topic || {};
},
result(r, key) {
if (this.saveMe) {
this.saveMe = false;
this.updateLastVisitedTopic(this.topic.id);
}
} }
} }
} }
@ -75,7 +84,8 @@
modules: { modules: {
edges: [] 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: { methods: {
openVideo() { openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId); 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})
}
}
}
});
} }
}, },
} }

View File

@ -1,5 +1,5 @@
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock 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): class BookMutations(object):
@ -8,3 +8,4 @@ class BookMutations(object):
delete_content_block = DeleteContentBlock.Field() delete_content_block = DeleteContentBlock.Field()
update_solution_visibility = UpdateSolutionVisibility.Field() update_solution_visibility = UpdateSolutionVisibility.Field()
update_last_module = UpdateLastModule.Field() update_last_module = UpdateLastModule.Field()
update_last_topic = UpdateLastTopic.Field()

View File

@ -2,8 +2,8 @@ import graphene
from graphene import relay from graphene import relay
from api.utils import get_errors, get_object from api.utils import get_errors, get_object
from books.models import Module from books.models import Module, Topic
from books.schema.queries import ModuleNode from books.schema.queries import ModuleNode, TopicNode
class UpdateSolutionVisibility(relay.ClientIDMutation): class UpdateSolutionVisibility(relay.ClientIDMutation):
@ -71,3 +71,26 @@ class UpdateLastModule(relay.ClientIDMutation):
except Exception as e: except Exception as e:
errors = ['Error: {}'.format(e)] errors = ['Error: {}'.format(e)]
return cls(errors=errors) 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)

View File

@ -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'),
),
]

View File

@ -17,6 +17,7 @@ DEFAULT_SCHOOL_ID = 1
class User(AbstractUser): class User(AbstractUser):
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True) 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='') avatar_url = models.CharField(max_length=254, blank=True, default='')
email = models.EmailField(_('email address'), unique=True) email = models.EmailField(_('email address'), unique=True)
hep_id = models.PositiveIntegerField(null=True, blank=False) hep_id = models.PositiveIntegerField(null=True, blank=False)

View File

@ -50,7 +50,8 @@ class UserNode(DjangoObjectType):
class Meta: class Meta:
model = User model = User
filter_fields = ['username', 'email'] 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'] 'selected_class', 'expiry_date']
interfaces = (relay.Node,) interfaces = (relay.Node,)
@ -64,7 +65,7 @@ class UserNode(DjangoObjectType):
return self.selected_class() return self.selected_class()
def resolve_expiry_date(self, info): 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 return format(datetime(2020, 7, 31), 'U') # just set some expiry date
else: else:
return self.license_expiry_date return self.license_expiry_date