aboutsummaryrefslogtreecommitdiff
path: root/resources/vue/mixins/courseware/container.js
blob: b25c1fb0c244e450f45106e4594052256cbddb9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import { mapActions, mapGetters } from 'vuex';

const containerMixin = {
    computed: {
        ...mapGetters({
            blockAdder: 'blockAdder',
            blockById: 'courseware-blocks/byId',
            containerById: 'courseware-containers/byId',
            pluginManager: 'pluginManager',
            lastCreatedBlock: 'courseware-blocks/lastCreated',
            lastCreatedContainers: 'courseware-containers/lastCreated',
            blockedByAnotherUser: 'currentElementBlockedByAnotherUser',
            currentStructuralElement: 'currentStructuralElement',
        }),
    },
    created: function () {
        this.pluginManager.registerComponentsLocally(this);
    },
    emits: ['select', 'containerReady'],
    methods: {
        ...mapActions({
            updateBlock: 'updateBlock',
            updateContainer: 'updateContainer',
            loadContainer: 'courseware-containers/loadById',
            loadBlock: 'courseware-blocks/loadById',
            loadStructuralElement: 'loadStructuralElement',
            lockObject: 'lockObject',
            unlockObject: 'unlockObject',
            createBlock: 'createBlockInContainer',
            createContainer: 'createContainer',
            companionInfo: 'companionInfo',
            companionSuccess: 'companionSuccess',
            companionWarning: 'companionWarning',
            sortContainersInStructualElements: 'sortContainersInStructualElements',
            setAdderStorage: 'coursewareBlockAdder',
            setProcessing: 'setProcessing',
            containerUpdate: 'courseware-containers/update'
        }),
        dropBlock(e) {
            this.isDragging = false; // implemented by each container type
            let data = {};
            data.originContainerId = e.from.dataset.containerId;
            data.targetContainerId = e.to.dataset.containerId;
            if (data.originContainerId === data.targetContainerId) {
                this.storeSort(); // implemented by each container type
            } else {
                data.originSectionId = e.from.dataset.sectionId;
                data.originSectionBlockList = [...e.from.children].map(b => b.dataset.blockId);
                data.targetSectionId = e.to.dataset.sectionId;
                data.targetSectionBlockList = [...e.to.children].map(b => b.dataset.blockId);
                data.blockId = e.item.dataset.blockId;
                data.newPos = e.newIndex;
                const indexInBlockList = data.targetSectionBlockList.findIndex(b => b === data.blockId);
                data.targetSectionBlockList.splice(data.newPos, 0, data.targetSectionBlockList.splice(indexInBlockList,1)[0]); // move block id to new position
                this.storeInAnotherContainer(data);
            }
        },
        async storeInAnotherContainer(data) {
            this.setProcessing(true);
            // update origin container
            if (data.originContainerId) {
                await this.lockObject({ id: data.originContainerId, type: 'courseware-containers' });
                await this.loadContainer({ id : data.originContainerId });
                let originContainer = this.containerById({ id: data.originContainerId});
                originContainer.attributes.payload.sections[data.originSectionId].blocks = data.originSectionBlockList;
                await this.containerUpdate(
                    originContainer,
                );
                await this.unlockObject({ id: data.originContainerId, type: 'courseware-containers' });
            }
            // update target container
            await this.lockObject({ id: data.targetContainerId, type: 'courseware-containers' });
            await this.loadContainer({ id : data.targetContainerId });
            let targetContainer = this.containerById({ id: data.targetContainerId});
            targetContainer.attributes.payload.sections[data.targetSectionId].blocks = data.targetSectionBlockList;
            await this.containerUpdate(
                targetContainer,
            );
            await this.unlockObject({ id: data.targetContainerId, type: 'courseware-containers' });

            // update block container id
            let block = this.blockById({id: data.blockId });
            block.relationships.container.data.id = data.targetContainerId;
            block.attributes.position = data.newPos;
            await this.lockObject({ id: block.id, type: 'courseware-blocks' });
            await this.updateBlock({
                block: block,
                containerId: data.targetContainerId,
            });
            await this.unlockObject({ id: block.id, type: 'courseware-blocks' });
            await this.loadBlock({ id: block.id });
            await this.loadContainer({ id : data.originContainerId });
            this.setProcessing(false);
        },
        checkSimpleArrayEquality(firstSet, secondSet) {
            return Array.isArray(firstSet) && Array.isArray(secondSet) &&
                firstSet.length === secondSet.length &&
                firstSet.every((val, index) => val === secondSet[index]);
        },
        async addNewBlock() {
            if (this.blockAdder.container) {
                const targetContainer = this.blockAdder.container;
                const section = this.blockAdder.section;
                const type = this.blockAdder.type;
                const position = this.blockAdder.position;

                try {
                    await this.lockObject({ id: targetContainer.id, type: 'courseware-containers' });
                } catch (error) {
                    if (error.status === 409) {
                        this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
                    } else {
                        console.log(error);
                    }
                }

                await this.createBlock({
                    container: targetContainer,
                    section: section,
                    blockType: type,
                });
                // get the just created block to add it to a container and adjust its position if applicable
                const newBlock = this.lastCreatedBlock;

                // if the block is dropped to a specific position, save it at the correct position
                if (position !== false) {
                    targetContainer.attributes.payload.sections[section].blocks.splice(
                        position, 0, newBlock.id);
                // otherwise put it in the last position of the last container
                } else {
                    targetContainer.attributes.payload.sections[section].blocks.push(newBlock.id);
                }

                const structuralElementId = targetContainer.relationships['structural-element'].data.id;

                await this.updateContainer({ container: targetContainer, structuralElementId: structuralElementId });
                await this.unlockObject({ id: targetContainer.id, type: 'courseware-containers' });
                this.companionSuccess({
                    info: this.$gettext('Der Block wurde erfolgreich eingefügt.'),
                });
            } else {
                // companion action
                this.companionWarning({
                    info: this.$gettext('Bitte fügen Sie der Seite einen Abschnitt hinzu, damit der Block eingefügt werden kann.'),
                });
            }
        },
        async sortClipboardBlock() {
            const targetContainer = this.blockAdder.container;
            const position = this.blockAdder.position;

            try {
                await this.lockObject({ id: targetContainer.id, type: 'courseware-containers' });
            } catch (error) {
                if (error.status === 409) {
                    this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
                } else {
                    console.log(error);
                }
            }
            const containerBlocks = targetContainer.attributes.payload.sections[this.blockAdder.section].blocks;
            containerBlocks.splice(position, 0, containerBlocks.pop());

            const structuralElementId = targetContainer.relationships['structural-element'].data.id;
            try {
                await this.updateContainer({ container: targetContainer, structuralElementId: structuralElementId });
                await this.unlockObject({ id: targetContainer.id, type: 'courseware-containers' });
            } catch (error) {
                this.companionWarning({
                    info: this.$gettext('Der Block konnte nicht hinzugefügt werden, bitte versuchen Sie es erneut.'),
                });
                console.log(error);
            }
        },
        async addContainer(data) {
            const type = data.type;
            const colspan = data.colspan;
            const firstSection = data.sections.firstSection;
            const secondSection = data.sections.secondSection;

            let attributes = {};
            attributes["container-type"] = type;
            let sections = [];
            if (type === 'list') {
                sections = [{ name: firstSection, icon: '', blocks: [] }];
            } else {
                sections = [{ name: firstSection, icon: '', blocks: [] },{ name: secondSection, icon: '', blocks: [] }];
            }
            attributes.payload = {
                colspan: colspan,
                sections: sections,
            };
            await this.createContainer({ structuralElementId: this.$route.params.id, attributes: attributes });
            this.companionSuccess({
                info: this.$gettext('Der Abschnitt wurde erfolgreich eingefügt.'),
            });

            // if the container was dropped to a specific position, sort it and update the structural element
            if (data.newPosition != null) {
                this.sortContainer(data.newPosition);
            }
        },
        async sortContainer(newContainerPos) {
            if (this.blockedByAnotherUser) {
                this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
                return false;
            }
            try {
                await this.lockObject({ id: this.currentStructuralElement.id, type: 'courseware-structural-elements' });
            } catch (error) {
                if (error.status === 409) {
                    this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
                } else {
                    console.log(error);
                }

                return false;
            }
            // insert the newly created container at the correct position
            let containerList = [];
            this.currentStructuralElement.relationships.containers.data.forEach(container => {
                containerList.push(container);
            });

            if (newContainerPos != null) {
                // find the container with the highest index (= latest addition) because it isn't
                // added at the bottom when it is a clipboard
                const highestIndexContainer = containerList.reduce((previous, current) => {
                    return (previous && parseInt(previous.id) > parseInt(current.id)) ? previous : current;
                }, 0);

                // get the last created container if a new container is added, or
                // the highest index container in the case of a clipboard
                const newestContainer = this.lastCreatedContainers?.id || highestIndexContainer.id;
                const tempPosition = containerList.findIndex(x => x.id === newestContainer);
                const newContainer = containerList.splice(tempPosition, 1)[0];

                if (newContainerPos === 'last') {
                    newContainerPos = containerList.length;
                }
                containerList.splice(newContainerPos, 0, newContainer);
            }
            await this.sortContainersInStructualElements({
                structuralElement: this.currentStructuralElement,
                containers: containerList,
            });
            await this.loadStructuralElement(this.currentStructuralElement.id);

            this.$emit('select', this.currentStructuralElement.id);

            return false;
        },
        resetAdderStorage() {
            // choose the last container and its last section as the default adder slot
            // for adding blocks and containers via click
            if (this.containers && this.containers.length > 0) {
                const lastContainer = this.containers[this.containers.length - 1];
                const section = lastContainer.activeSection ?? 0;
                this.setAdderStorage({
                    container: lastContainer,
                    section: section
                });
            }
        },

        onAllBlocksReady(data) {
            this.$emit('containerReady', data);
        }

    }
};

export default containerMixin;