From 69dac2cf7f9d0569c8b8e355d8d036bda0fc162e Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 22 Jun 2022 19:55:44 +0200 Subject: [PATCH 01/26] Refactor room pages --- client/src/pages/{ => rooms}/editRoom.vue | 0 client/src/pages/{ => rooms}/newRoom.vue | 0 client/src/pages/{ => rooms}/room.vue | 0 client/src/pages/{ => rooms}/rooms.vue | 0 client/src/router/room.routes.js | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename client/src/pages/{ => rooms}/editRoom.vue (100%) rename client/src/pages/{ => rooms}/newRoom.vue (100%) rename client/src/pages/{ => rooms}/room.vue (100%) rename client/src/pages/{ => rooms}/rooms.vue (100%) diff --git a/client/src/pages/editRoom.vue b/client/src/pages/rooms/editRoom.vue similarity index 100% rename from client/src/pages/editRoom.vue rename to client/src/pages/rooms/editRoom.vue diff --git a/client/src/pages/newRoom.vue b/client/src/pages/rooms/newRoom.vue similarity index 100% rename from client/src/pages/newRoom.vue rename to client/src/pages/rooms/newRoom.vue diff --git a/client/src/pages/room.vue b/client/src/pages/rooms/room.vue similarity index 100% rename from client/src/pages/room.vue rename to client/src/pages/rooms/room.vue diff --git a/client/src/pages/rooms.vue b/client/src/pages/rooms/rooms.vue similarity index 100% rename from client/src/pages/rooms.vue rename to client/src/pages/rooms/rooms.vue diff --git a/client/src/router/room.routes.js b/client/src/router/room.routes.js index e1053d11..c92748d1 100644 --- a/client/src/router/room.routes.js +++ b/client/src/router/room.routes.js @@ -1,9 +1,9 @@ import {NEW_ROOM_PAGE, ROOMS_PAGE} from '@/router/room.names'; -const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms'); -const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/newRoom'); -const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/editRoom'); -const room = () => import(/* webpackChunkName: "rooms" */'@/pages/room'); +const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/rooms'); +const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoom'); +const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoom'); +const room = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/room'); const moduleRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/module/moduleRoom'); export default [ From 926b31d1f506f1401a0d82b6a242fe38ea4807ca Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 22 Jun 2022 19:58:31 +0200 Subject: [PATCH 02/26] Refactor content block element chooser to be more dynamic --- .../content-forms/ChooserElement.vue | 76 ++++++++ .../ContentBlockElementChooserWidget.vue | 162 +++++++----------- 2 files changed, 138 insertions(+), 100 deletions(-) create mode 100644 client/src/components/content-forms/ChooserElement.vue diff --git a/client/src/components/content-forms/ChooserElement.vue b/client/src/components/content-forms/ChooserElement.vue new file mode 100644 index 00000000..5a8672d9 --- /dev/null +++ b/client/src/components/content-forms/ChooserElement.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue index 3df6777b..196d20df 100644 --- a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue +++ b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue @@ -28,77 +28,14 @@ :class="{'content-block-element-chooser-widget--no-assignment': hideAssignment}" class="content-block-element-chooser-widget" > - - - - - - - + @@ -107,8 +44,8 @@ import Checkbox from '@/components/ui/Checkbox'; import formElementIcons from '@/components/ui/form-element-icons'; - import TitleIcon from '@/components/icons/TitleIcon'; import CrossIcon from '@/components/icons/CrossIcon'; + import ChooserElement from '@/components/content-forms/ChooserElement'; export default { props: { @@ -125,15 +62,63 @@ }, components: { + ChooserElement, CrossIcon, - TitleIcon, Checkbox, ...formElementIcons, }, - data: () => ({ - convertToList: false, - }), + data() { + return { + convertToList: false, + chooserTypes: [ + { + type: 'subtitle', + block: 'subtitle', + title: 'Untertitel', + icon: 'title-icon', + }, + { + type: 'link', + block: 'link_block', + title: 'Link', + icon: 'link-icon', + }, + { + type: 'video', + block: 'video_block', + }, + { + type: 'image', + block: 'image_url_block', + title: 'Bild', + }, + { + type: 'text', + block: 'text_block', + }, + { + type: 'assignment', + block: 'assignment', + icon: 'speech-bubble-icon', + title: 'Aufgabe & Ergebnis', + }, + { + type: 'document', + block: 'document_block', + title: 'Dokument', + }, + + + ], + }; + }, + + computed: { + filteredChooserTypes() { + return this.chooserTypes.filter(type => !("show" in type) || type.show ); // display element if `show` is not set or if `show` evaluates to true + }, + }, methods: { changeType(type) { @@ -144,7 +129,7 @@ }, remove() { this.$emit('remove'); - } + }, }, }; @@ -222,29 +207,6 @@ grid-row: 1; } - &__link { - cursor: pointer; - border: 1px solid $color-silver; - border-radius: 4px; - height: 105px; - width: 105px; - box-sizing: border-box; - display: grid; - grid-template-rows: 1fr 45px; - justify-content: center; - justify-items: center; - align-items: center; - } - &__link-icon { - width: 40px; - height: 40px; - align-self: end; - } - - &__link-text { - font-size: toRem(13px); - align-self: start; - } } From 3b3d485c76f4965343679ce19b1394e04d4ac8cf Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 22 Jun 2022 20:00:51 +0200 Subject: [PATCH 03/26] Add new page for adding a new room entry --- .../components/rooms/AddRoomEntryButton.vue | 21 ++++++------ client/src/pages/rooms/newRoomEntry.vue | 32 +++++++++++++++++++ client/src/router/room.names.js | 1 + client/src/router/room.routes.js | 4 ++- 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 client/src/pages/rooms/newRoomEntry.vue diff --git a/client/src/components/rooms/AddRoomEntryButton.vue b/client/src/components/rooms/AddRoomEntryButton.vue index eded29e1..2a7157af 100644 --- a/client/src/components/rooms/AddRoomEntryButton.vue +++ b/client/src/components/rooms/AddRoomEntryButton.vue @@ -1,29 +1,32 @@ diff --git a/client/src/pages/rooms/newRoomEntry.vue b/client/src/pages/rooms/newRoomEntry.vue new file mode 100644 index 00000000..d4915711 --- /dev/null +++ b/client/src/pages/rooms/newRoomEntry.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/client/src/router/room.names.js b/client/src/router/room.names.js index a7dd05a2..c86a0087 100644 --- a/client/src/router/room.names.js +++ b/client/src/router/room.names.js @@ -1,2 +1,3 @@ export const NEW_ROOM_PAGE = 'new-room'; export const ROOMS_PAGE = 'rooms'; +export const ADD_ROOM_ENTRY_PAGE = 'add-room-entry'; diff --git a/client/src/router/room.routes.js b/client/src/router/room.routes.js index c92748d1..5fc71a6c 100644 --- a/client/src/router/room.routes.js +++ b/client/src/router/room.routes.js @@ -1,9 +1,10 @@ -import {NEW_ROOM_PAGE, ROOMS_PAGE} from '@/router/room.names'; +import {NEW_ROOM_PAGE, ROOMS_PAGE, ADD_ROOM_ENTRY_PAGE} from '@/router/room.names'; const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/rooms'); const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoom'); const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoom'); const room = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/room'); +const newRoomEntry = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoomEntry'); const moduleRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/module/moduleRoom'); export default [ @@ -11,6 +12,7 @@ export default [ {path: '/new-room/', name: NEW_ROOM_PAGE, component: newRoom}, {path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true}, {path: '/room/:slug', name: 'room', component: room, props: true}, + {path: '/room/:slug/add', name: ADD_ROOM_ENTRY_PAGE, component: newRoomEntry, props: true}, { path: '/module-room/:slug', name: 'moduleRoom', From 0060889a63b1e1dbc542ec38ef5acd5739339338 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 22 Jun 2022 20:01:54 +0200 Subject: [PATCH 04/26] Hide element types depending on feature set --- .../content-block-form/ContentBlockForm.vue | 19 ++++++++++++++++++- .../ContentBlockElementChooserWidget.vue | 13 ++++++++++++- client/src/consts/features.consts.js | 2 ++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 client/src/consts/features.consts.js diff --git a/client/src/components/content-block-form/ContentBlockForm.vue b/client/src/components/content-block-form/ContentBlockForm.vue index f8901aa2..0c3f7a6e 100644 --- a/client/src/components/content-block-form/ContentBlockForm.vue +++ b/client/src/components/content-block-form/ContentBlockForm.vue @@ -14,6 +14,7 @@ :checked="localContentBlock.isAssignment" class="content-block-form__task-toggle" label="Inhaltsblock als Auftrag formatieren" + v-if="hasDefaultFeatures" @input="localContentBlock.isAssignment=$event" /> @@ -139,8 +140,12 @@ import {CHOOSER, transformInnerContents} from '@/components/content-block-form/helpers.js'; import ContentElementActions from '@/components/content-block-form/ContentElementActions.vue'; import {ContentBlock, numberOrUndefined} from "@/@types"; + import {DEFAULT_FEATURES} from "@/consts/features.consts"; // TODO: refactor this file, it's huuuuuge! + interface ContentBlockFormData { + localContentBlock: any; + } export default Vue.extend({ props: { @@ -152,6 +157,15 @@ type: Object as PropType, required: true, }, + features: { + type: String, + default: DEFAULT_FEATURES + } + }, + provide(): object { + return { + features: this.features + }; }, components: { ContentElementActions, @@ -161,7 +175,7 @@ ContentFormSection, Toggle, }, - data() { + data(): ContentBlockFormData { return { localContentBlock: Object.assign({}, { title: this.contentBlock.title, @@ -176,6 +190,9 @@ isValid(): boolean { return this.localContentBlock.title > ''; }, + hasDefaultFeatures(): boolean { + return this.features === DEFAULT_FEATURES; + } }, methods: { update(index: number, element: any, parent?: number) { diff --git a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue index 196d20df..5742b99d 100644 --- a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue +++ b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue @@ -13,7 +13,7 @@ Neuer Inhalt diff --git a/client/src/components/rooms/RoomEntryActions.vue b/client/src/components/rooms/RoomEntryActions.vue index 16549cb0..0c4364f7 100644 --- a/client/src/components/rooms/RoomEntryActions.vue +++ b/client/src/components/rooms/RoomEntryActions.vue @@ -3,13 +3,13 @@ @@ -19,10 +19,11 @@ import MoreActions from '@/components/rooms/MoreActions'; import DELETE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/deleteRoomEntry'; import ROOM_ENTRIES_QUERY from 'gql/queries/roomEntriesQuery'; + import {UPDATE_ROOM_ENTRY_PAGE} from '@/router/room.names'; export default { props: { - id: { + slug: { type: String, default: '', }, @@ -40,12 +41,12 @@ }, methods: { - deleteRoomEntry(id) { + deleteRoomEntry(slug) { this.$apollo.mutate({ mutation: DELETE_ROOM_ENTRY_MUTATION, variables: { input: { - id, + slug, }, }, update(store, {data: {deleteRoomEntry: {success, roomSlug}}}) { @@ -54,15 +55,22 @@ const variables = {slug: roomSlug}; const data = store.readQuery({query, variables}); if (data) { - data.room.roomEntries.edges.splice(data.room.roomEntries.edges.findIndex(edge => edge.node.id === id), 1); + // todo: make immutable + data.room.roomEntries.edges.splice(data.room.roomEntries.edges.findIndex(edge => edge.node.slug === slug), 1); store.writeQuery({query, data, variables}); } } }, }); }, - editRoomEntry(id) { - this.$store.dispatch('editRoomEntry', id); + editRoomEntry(slug) { + this.$router.push({ + name: UPDATE_ROOM_ENTRY_PAGE, + params: { + slug: this.$route.params.slug, + entrySlug: slug + } + }); }, }, }; diff --git a/client/src/components/rooms/room-entries/NewRoomEntryWizard.vue b/client/src/components/rooms/room-entries/NewRoomEntryWizard.vue deleted file mode 100644 index 06aebd61..00000000 --- a/client/src/components/rooms/room-entries/NewRoomEntryWizard.vue +++ /dev/null @@ -1,92 +0,0 @@ - - - diff --git a/client/src/pages/rooms/editRoomEntry.vue b/client/src/pages/rooms/editRoomEntry.vue new file mode 100644 index 00000000..ad630a8c --- /dev/null +++ b/client/src/pages/rooms/editRoomEntry.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/client/src/pages/rooms/newRoomEntry.vue b/client/src/pages/rooms/newRoomEntry.vue index 124998ed..04c46636 100644 --- a/client/src/pages/rooms/newRoomEntry.vue +++ b/client/src/pages/rooms/newRoomEntry.vue @@ -52,7 +52,7 @@ const entry = { title, contents, - slug: this.slug + roomSlug: this.slug }; this.$apollo.mutate({ mutation: NEW_ROOM_ENTRY_MUTATION, diff --git a/client/src/router/room.names.js b/client/src/router/room.names.js index f99a3a30..6edd6e8a 100644 --- a/client/src/router/room.names.js +++ b/client/src/router/room.names.js @@ -2,3 +2,4 @@ export const NEW_ROOM_PAGE = 'new-room'; export const ROOMS_PAGE = 'rooms'; export const ROOM_PAGE = 'room'; export const ADD_ROOM_ENTRY_PAGE = 'add-room-entry'; +export const UPDATE_ROOM_ENTRY_PAGE = 'update-room-entry'; diff --git a/client/src/router/room.routes.js b/client/src/router/room.routes.js index d1e4e0e0..c1f15b50 100644 --- a/client/src/router/room.routes.js +++ b/client/src/router/room.routes.js @@ -1,10 +1,11 @@ -import {NEW_ROOM_PAGE, ROOMS_PAGE, ADD_ROOM_ENTRY_PAGE, ROOM_PAGE} from '@/router/room.names'; +import {NEW_ROOM_PAGE, ROOMS_PAGE, ADD_ROOM_ENTRY_PAGE, ROOM_PAGE, UPDATE_ROOM_ENTRY_PAGE} from '@/router/room.names'; const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/rooms'); const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoom'); const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoom'); const room = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/room'); const newRoomEntry = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoomEntry'); +const editRoomEntry = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoomEntry'); const moduleRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/module/moduleRoom'); export default [ @@ -13,6 +14,7 @@ export default [ {path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true}, {path: '/room/:slug', name: ROOM_PAGE, component: room, props: true}, {path: '/room/:slug/add', name: ADD_ROOM_ENTRY_PAGE, component: newRoomEntry, props: true}, + {path: '/room/:slug/edit/:entrySlug', name: UPDATE_ROOM_ENTRY_PAGE, component: editRoomEntry, props: true}, { path: '/module-room/:slug', name: 'moduleRoom', diff --git a/server/rooms/inputs.py b/server/rooms/inputs.py index 499468ca..6e7a2d8d 100644 --- a/server/rooms/inputs.py +++ b/server/rooms/inputs.py @@ -26,8 +26,8 @@ class RoomEntryArgument(InputObjectType): class AddRoomEntryArgument(RoomEntryArgument): - slug = graphene.String(required=True) + room_slug = graphene.String(required=True) class UpdateRoomEntryArgument(RoomEntryArgument): - id = graphene.ID(required=True) + slug = graphene.String(required=True) diff --git a/server/rooms/mutations.py b/server/rooms/mutations.py index 74606e9b..a4a033b5 100644 --- a/server/rooms/mutations.py +++ b/server/rooms/mutations.py @@ -86,13 +86,13 @@ class MutateRoomEntry(relay.ClientIDMutation): def mutate_and_get_payload(cls, root, info, **kwargs): room_entry_data = kwargs.get('room_entry') room = None - slug = room_entry_data.get('slug') + room_slug = room_entry_data.get('room_slug') - if slug is not None: - room = Room.objects.get(slug=slug) + if room_slug is not None: + room = Room.objects.get(slug=room_slug) room_entry_data['room'] = room.id - if room_entry_data.get('id') is not None: + if room_entry_data.get('slug') is not None: serializer = cls.update_room_entry(info, room_entry_data) else: serializer = cls.add_room_entry(info, room_entry_data, room) @@ -106,7 +106,7 @@ class MutateRoomEntry(relay.ClientIDMutation): @classmethod def update_room_entry(cls, info, room_entry_data): - instance = get_object(RoomEntry, room_entry_data.get('id')) + instance = RoomEntry.objects.get(slug=room_entry_data.get('slug')) if not instance.room.school_class.is_user_in_schoolclass(info.context.user): raise Exception('You are in the wrong class') diff --git a/server/schema.graphql b/server/schema.graphql index 6eea61e0..93b685fc 100644 --- a/server/schema.graphql +++ b/server/schema.graphql @@ -118,7 +118,7 @@ input AddRoomArgument { input AddRoomEntryArgument { title: String! contents: [ContentElementInput] - slug: String! + roomSlug: String! } input AddRoomEntryInput { From a463f96167b5bad7572f2e7bd01df3f0ab57af14 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 27 Jun 2022 17:00:29 +0200 Subject: [PATCH 14/26] Update cypress test, fix it --- .../frontend/rooms/room-page.spec.js | 65 ++++++++++++------- client/src/components/rooms/RoomActions.vue | 2 - client/src/components/ui/PopoverLink.vue | 1 - 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/client/cypress/integration/frontend/rooms/room-page.spec.js b/client/cypress/integration/frontend/rooms/room-page.spec.js index e98dd005..c3520550 100644 --- a/client/cypress/integration/frontend/rooms/room-page.spec.js +++ b/client/cypress/integration/frontend/rooms/room-page.spec.js @@ -186,20 +186,36 @@ describe('The Room Page', () => { const {me} = MeQuery; const id = atob(me.id).split(':')[1]; const authorId = btoa(`PublicUserNode:${id}`); + const entrySlug = 'entry-slug'; + const roomEntry = { + id: 'entry-id', + slug: entrySlug, + title: 'My Entry', + contents: [ + { + type: 'text_block', + value: { + text: 'some text' + } + } + ], + comments: [], + author: { + ...me, + id: authorId, + firstName: 'Hans', + lastName: 'Was Heiri', + avatarUrl: '' + }, + }; const room = { id: 'some-room', + slug, + // schoolClass: me.selectedClass, roomEntries: { edges: [ { - node: { - id: '', - slug: '', - contents: [], - author: { - ...me, - id: authorId, - }, - }, + node: roomEntry, }, ], }, @@ -209,7 +225,9 @@ describe('The Room Page', () => { RoomEntriesQuery: { room, }, - RoomEntryQuery: {}, + RoomEntryQuery: { + roomEntry + }, }; cy.mockGraphqlOps({ operations, @@ -217,6 +235,7 @@ describe('The Room Page', () => { cy.visit(`/room/${slug}`); cy.getByDataCy('room-entry-actions').click(); cy.getByDataCy('edit-room-entry').click(); + cy.location('pathname').should('include', entrySlug); }); it('creates a room entry', () => { @@ -249,39 +268,39 @@ describe('The Room Page', () => { const otherClass = { id: btoa('SchoolClassNode:34'), name: 'Other Class', - readOnly: false + readOnly: false, }; let selectedClass = me.selectedClass; const operations = { MeQuery: () => { return { - me: { - ...me, - schoolClasses: [...me.schoolClasses, otherClass], - selectedClass - }, - }; + me: { + ...me, + schoolClasses: [...me.schoolClasses, otherClass], + selectedClass, + }, + }; }, RoomEntriesQuery, UpdateSettings() { selectedClass = otherClass; return { updateSettings: { - success: true - } + success: true, + }, }; }, ModuleDetailsQuery: {}, MySchoolClassQuery: () => { return { me: { - selectedClass - } + selectedClass, + }, }; }, RoomsQuery: { - rooms: [] - } + rooms: [], + }, }; cy.mockGraphqlOps({ diff --git a/client/src/components/rooms/RoomActions.vue b/client/src/components/rooms/RoomActions.vue index b0e409c4..5ff1b960 100644 --- a/client/src/components/rooms/RoomActions.vue +++ b/client/src/components/rooms/RoomActions.vue @@ -21,7 +21,6 @@