Merged in feature/content-block-instrument-type-MS-480 (pull request #119)

Feature/content block instrument type MS-480

Approved-by: Christian Cueni
This commit is contained in:
Ramon Wenger 2022-09-19 13:05:44 +00:00
commit 6476d09f6d
18 changed files with 172 additions and 25 deletions

View File

@ -26,7 +26,11 @@ describe('Instruments on Module page', () => {
title: 'Some Chapter', title: 'Some Chapter',
contentBlocks: [ contentBlocks: [
{ {
'type': 'base_communication', 'type': 'instrument',
instrumentCategory: {
id: 'category-id',
name: 'Sprache & Kommunikation'
},
'title': 'Das Interview', 'title': 'Das Interview',
'contents': [ 'contents': [
{ {
@ -40,6 +44,7 @@ describe('Instruments on Module page', () => {
{ {
'type': 'normal', 'type': 'normal',
'title': 'Normaler Block', 'title': 'Normaler Block',
instrumentCategory: null,
'contents': [ 'contents': [
{ {
type: 'text_block', type: 'text_block',

View File

@ -5,6 +5,7 @@
> >
<div <div
:class="specialClass" :class="specialClass"
:style="instrumentStyle"
class="content-block" class="content-block"
data-cy="content-block" data-cy="content-block"
> >
@ -43,6 +44,7 @@
<h3 <h3
class="content-block__instrument-label" class="content-block__instrument-label"
data-cy="instrument-label" data-cy="instrument-label"
:style="instrumentLabelStyle"
v-if="instrumentLabel !== ''" v-if="instrumentLabel !== ''"
> >
{{ instrumentLabel }} {{ instrumentLabel }}
@ -129,13 +131,37 @@
specialClass() { specialClass() {
return `content-block--${this.contentBlock.type.toLowerCase()}`; return `content-block--${this.contentBlock.type.toLowerCase()}`;
}, },
isInstrumentBlock() {
return !!this.contentBlock.instrumentCategory;
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
instrumentStyle() {
if (this.isInstrumentBlock) {
return {
backgroundColor: this.contentBlock.instrumentCategory.background
};
}
return {};
},
instrumentLabel() { instrumentLabel() {
const contentType = this.contentBlock.type.toLowerCase(); const contentType = this.contentBlock.type.toLowerCase();
if (contentType.startsWith('base')) { // all instruments start with `base` if (contentType.startsWith('base')) { // all legacy instruments start with `base`
return instrumentCategory(contentType); return instrumentCategory(contentType);
} }
if (this.isInstrumentBlock) {
return instrumentCategory(this.contentBlock.instrumentCategory.name);
}
return ''; return '';
}, },
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
instrumentLabelStyle() {
if (this.isInstrumentBlock) {
return {
color: this.contentBlock.instrumentCategory.foreground
};
}
return {};
},
canEditContentBlock() { canEditContentBlock() {
return this.contentBlock.mine && !this.contentBlock.indent; return this.contentBlock.mine && !this.contentBlock.indent;
}, },
@ -316,6 +342,10 @@
} }
} }
&--instrument {
@include content-box-base;
}
/deep/ p { /deep/ p {
line-height: 1.5; line-height: 1.5;
margin-bottom: 1em; margin-bottom: 1em;

View File

@ -8,6 +8,9 @@
<router-link <router-link
:to="{name: 'instrument', params: { slug: value.slug }}" :to="{name: 'instrument', params: { slug: value.slug }}"
class="instrument-widget__button button" class="instrument-widget__button button"
:style="{
borderColor: value.foreground
}"
> >
{{ $flavor.textInstrument }} anzeigen {{ $flavor.textInstrument }} anzeigen
</router-link> </router-link>
@ -15,6 +18,7 @@
</template> </template>
<script> <script>
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
export default { export default {
props: ['value'], props: ['value'],
}; };

View File

@ -2,10 +2,17 @@
<a <a
:class="typeClass" :class="typeClass"
class="filter-entry" class="filter-entry"
:style="categoryStyle"
> >
<span class="filter-entry__text">{{ text }}</span> <span class="filter-entry__text">{{ text }}</span>
<span class="filter-entry__icon-wrapper"> <span
<chevron-right class="filter-entry__icon" /> :style="activeStyle"
class="filter-entry__icon-wrapper"
>
<chevron-right
:style="{fill: category.foreground}"
class="filter-entry__icon"
/>
</span> </span>
</a> </a>
@ -13,6 +20,7 @@
<script> <script>
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql'; import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql';
const ChevronRight = () => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronRight'); const ChevronRight = () => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronRight');
export default { export default {
@ -31,7 +39,7 @@
}, },
category: { category: {
type: Object, type: Object,
default: () => {}, default: () => ({}),
}, },
}, },
@ -41,8 +49,8 @@
apollo: { apollo: {
instrumentFilter: { instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY query: INSTRUMENT_FILTER_QUERY,
} },
}, },
data() { data() {
@ -56,12 +64,31 @@
computed: { computed: {
isActive() { isActive() {
if (!this.instrumentFilter.currentFilter) { if (!this.instrumentFilter.currentFilter) {
return this.type === ''; return this.id === '';
} }
// eslint-disable-next-line // eslint-disable-next-line
const [_, identifier] = this.instrumentFilter.currentFilter.split(':'); const [_, identifier] = this.instrumentFilter.currentFilter.split(':');
return this.type === identifier; return this.id === identifier;
}, },
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
activeStyle() {
if (this.isActive) {
return {
backgroundColor: this.category.background,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
categoryStyle() {
if (this.isCategory) {
return {
color: this.category.foreground,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
typeClass() { typeClass() {
return { return {
'filter-entry--active': this.isActive, 'filter-entry--active': this.isActive,

View File

@ -25,6 +25,7 @@
import {INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts'; import {INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts';
import {instrumentCategory} from '@/helpers/instrumentType'; import {instrumentCategory} from '@/helpers/instrumentType';
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
export default { export default {
props: { props: {
instrument: { instrument: {

View File

@ -3,6 +3,12 @@ fragment ContentBlockParts on ContentBlockNode {
slug slug
userCreated userCreated
mine mine
instrumentCategory {
id
foreground
background
name
}
bookmarks { bookmarks {
uuid uuid
note { note {

View File

@ -2,6 +2,8 @@ query InstrumentCategoriesQuery {
instrumentCategories { instrumentCategories {
name name
id id
foreground
background
types { types {
name name
id id

View File

@ -8,7 +8,7 @@ const instrumentType = (instrument) => {
} else { } else {
return instrument.type.category.name; return instrument.type.category.name;
} }
return typeDictionary[category] || ''; return typeDictionary[category] || category || '';
}; };
const instrumentCategory = (instrument) => { const instrumentCategory = (instrument) => {

View File

@ -61,18 +61,25 @@
} }
@mixin content-box($color-list) { @mixin content-box-base {
background-color: nth($color-list, 2);
padding: 15px; padding: 15px;
align-items: start; align-items: start;
border-radius: $default-border-radius; border-radius: $default-border-radius;
/deep/ .button { /deep/ .button {
border-color: nth($color-list, 1);
background-color: $color-white; background-color: $color-white;
} }
} }
@mixin content-box($color-list) {
@include content-box-base;
background-color: nth($color-list, 2);
/deep/ .button {
border-color: nth($color-list, 1);
}
}
@mixin desktop { @mixin desktop {
@media (min-width: 1200px) { @media (min-width: 1200px) {
@content @content

View File

@ -81,9 +81,10 @@ def augment_fields(raw_data):
logger.error('Survey {} does not exist'.format(survey_id)) logger.error('Survey {} does not exist'.format(survey_id))
if _type == 'basic_knowledge' or _type == 'instrument': if _type == 'basic_knowledge' or _type == 'instrument':
_value = data['value'] _value = data['value']
basic_knowledge = BasicKnowledge.objects.get(pk=_value['basic_knowledge']) instrument = BasicKnowledge.objects.get(pk=_value['basic_knowledge'])
_value.update({ _value.update({
'slug': basic_knowledge.slug 'slug': instrument.slug,
'foreground': instrument.new_type.category.foreground
}) })
data['value'] = _value data['value'] = _value

View File

@ -157,7 +157,7 @@ class ContentBlockFactory(BasePageFactory):
class Meta: class Meta:
model = ContentBlock model = ContentBlock
type = factory.LazyAttribute(lambda x: random.choice(['normal', 'base_communication', 'task', 'base_society'])) type = factory.LazyAttribute(lambda x: random.choice(['normal', 'instrument', 'task',]))
contents = wagtail_factories.StreamFieldFactory({ contents = wagtail_factories.StreamFieldFactory({
'text_block': TextBlockFactory, 'text_block': TextBlockFactory,

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-09-15 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0036_alter_contentblock_contents'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='type',
field=models.CharField(choices=[('normal', 'Normal'), ('base_communication', 'Instrument Sprache & Kommunikation'), ('task', 'Auftrag'), ('instrument', 'Instrument'), ('base_society', 'Instrument Gesellschaft'), ('base_interdisciplinary', 'Überfachliches Instrument')], default='normal', max_length=100),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.13 on 2022-09-15 13:40
from django.db import migrations
def migrate_instruments(apps, schema_editor):
ContentBlock = apps.get_model('books', 'ContentBlock')
ContentBlock.objects.filter(type__startswith='base_').update(type='instrument')
class Migration(migrations.Migration):
dependencies = [
('books', '0037_alter_contentblock_type'),
]
operations = [
migrations.RunPython(migrate_instruments, migrations.RunPython.noop)
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-09-15 14:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0038_auto_20220915_1340'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='type',
field=models.CharField(choices=[('normal', 'Normal'), ('task', 'Auftrag'), ('instrument', 'Instrument')], default='normal', max_length=100),
),
]

View File

@ -26,17 +26,13 @@ class ContentBlock(StrictHierarchyPage):
verbose_name_plural = 'Inhaltsblöcke' verbose_name_plural = 'Inhaltsblöcke'
NORMAL = 'normal' NORMAL = 'normal'
BASE_COMMUNICATION = 'base_communication'
TASK = 'task' TASK = 'task'
BASE_SOCIETY = 'base_society' INSTRUMENT = 'instrument'
BASE_INTERDISCIPLINARY = 'base_interdisciplinary'
TYPE_CHOICES = ( TYPE_CHOICES = (
(NORMAL, 'Normal'), (NORMAL, 'Normal'),
(BASE_COMMUNICATION, 'Instrument Sprache & Kommunikation'),
(TASK, 'Auftrag'), (TASK, 'Auftrag'),
(BASE_SOCIETY, 'Instrument Gesellschaft'), (INSTRUMENT, 'Instrument'),
(BASE_INTERDISCIPLINARY, 'Überfachliches Instrument'),
) )
# blocks without owner are visible by default, need to be hidden for each class # blocks without owner are visible by default, need to be hidden for each class

View File

@ -2,6 +2,8 @@ import graphene
from graphene import relay from graphene import relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentCategoryNode
from books.models import ContentBlock from books.models import ContentBlock
from books.schema.interfaces.contentblock import ContentBlockInterface from books.schema.interfaces.contentblock import ContentBlockInterface
from books.utils import are_solutions_enabled_for from books.utils import are_solutions_enabled_for
@ -40,6 +42,7 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
mine = graphene.Boolean() mine = graphene.Boolean()
bookmarks = graphene.List(ContentBlockBookmarkNode) bookmarks = graphene.List(ContentBlockBookmarkNode)
original_creator = graphene.Field('users.schema.PublicUserNode') original_creator = graphene.Field('users.schema.PublicUserNode')
instrument_category = graphene.Field(InstrumentCategoryNode)
class Meta: class Meta:
model = ContentBlock model = ContentBlock
@ -80,6 +83,17 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
content_block=self content_block=self
) )
@staticmethod
def resolve_instrument_category(root: ContentBlock, info, **kwargs):
if root.type == ContentBlock.INSTRUMENT:
for content in root.contents.raw_data:
if content['type'] == 'instrument' or content['type'] == 'basic_knowledge':
_id = content['value']['basic_knowledge']
instrument = BasicKnowledge.objects.get(id=_id)
category = instrument.new_type.category
return category
return None
def process_module_room_slug_block(content): def process_module_room_slug_block(content):
if content['type'] == 'module_room_slug': if content['type'] == 'module_room_slug':

View File

@ -203,7 +203,7 @@ module_1_chapter_2 = {
'description': 'Haben Sie sich beim Shoppen schon mal überlegt, aus welchem Beweggrund Sie ein bestimmtes Produkt eigentlich unbedingt haben wollten? Wir gehen im Folgenden anhand Ihres letzten Kleiderkaufs dieser Frage nach.', 'description': 'Haben Sie sich beim Shoppen schon mal überlegt, aus welchem Beweggrund Sie ein bestimmtes Produkt eigentlich unbedingt haben wollten? Wir gehen im Folgenden anhand Ihres letzten Kleiderkaufs dieser Frage nach.',
'content_blocks': [ 'content_blocks': [
{ {
'type': 'base_society', 'type': 'instrument',
'title': 'Das Berufsbildungssystem', 'title': 'Das Berufsbildungssystem',
'contents': [ 'contents': [
{ {

View File

@ -299,6 +299,7 @@ type ContentBlockNode implements Node & ContentBlockInterface {
mine: Boolean mine: Boolean
bookmarks: [ContentBlockBookmarkNode] bookmarks: [ContentBlockBookmarkNode]
originalCreator: PublicUserNode originalCreator: PublicUserNode
instrumentCategory: InstrumentCategoryNode
} }
type ContentBlockNodeConnection { type ContentBlockNodeConnection {
@ -511,7 +512,7 @@ type InstrumentBookmarkNode implements Node {
instrument: InstrumentNode! instrument: InstrumentNode!
} }
type InstrumentCategoryNode { type InstrumentCategoryNode implements Node {
id: ID! id: ID!
name: String! name: String!
background: String! background: String!
@ -539,7 +540,7 @@ type InstrumentNodeEdge {
cursor: String! cursor: String!
} }
type InstrumentTypeNode { type InstrumentTypeNode implements Node {
id: ID! id: ID!
name: String! name: String!
category: InstrumentCategoryNode category: InstrumentCategoryNode