Skip to content
Snippets Groups Projects
Forked from Stud.IP / Stud.IP
623 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
courseware.module.js 54.47 KiB
import axios from 'axios';

const getDefaultState = () => {
    return {
        blockAdder: {},
        containerAdder: false,
        consumeMode: false,
        context: {},
        courseware: {},
        currentElement: {},
        licenses: null, // we need a route for License SORM
        httpClient: null,
        lastElement: null,
        msg: 'Dehydrated',
        msgCompanionOverlay: '',
        styleCompanionOverlay: 'default',
        pluginManager: null,
        showCompanionOverlay: false,
        showToolbar: false,
        selectedToolbarItem: 'contents',
        urlHelper: null,
        userId: null,
        viewMode: 'read',
        dashboardViewMode: 'default',
        userIsTeacherInCourse: false,
        showStructuralElementEditDialog: false,
        showStructuralElementAddDialog: false,
        showStructuralElementAddChooserDialog: false,
        showStructuralElementImportDialog: false,
        showStructuralElementCopyDialog: false,
        showStructuralElementLinkDialog: false,
        showStructuralElementExportDialog: false,
        showStructuralElementExportChooserDialog: false,
        showStructuralElementPdfExportDialog: false,
        showStructuralElementInfoDialog: false,
        showStructuralElementDeleteDialog: false,
        showStructuralElementOerDialog: false,
        showStructuralElementPublicLinkDialog: false,
        showStructuralElementRemoveLockDialog: false,
        showStructuralElementFeedbackDialog: false,
        showStructuralElementFeedbackCreateDialog: false,

        showSuggestOerDialog: false,

        importFilesState: '',
        importFilesProgress: 0,
        importStructuresState: '',
        importStructuresProgress: 0,
        importErrors: [],

        exportState: '',
        exportProgress: 0,

        permissionFilter: 'read',
        purposeFilter: 'all',
        sourceFilter: 'all',
        showOverviewElementAddDialog: false,

        bookmarkFilter: 'all',

        showSearchResults: false,
        searchResults: [],

        assistiveLiveContents: '',
        progresses: null,

        toolbarActive: true,
        hideEditLayout: false,
        feedbackSettings: null,
        processing: false,
    };
};

const initialState = getDefaultState();

const getters = {
    msg(state) {
        return state.msg;
    },
    currentUser(state, getters, rootState, rootGetters) {
        const id = getters.userId;
        return rootGetters['users/byId']({ id });
    },
    lastElement(state) {
        return state.lastElement;
    },
    courseware(state) {
        return state.courseware;
    },
    rootLayout(state, getters) {
        return getters.courseware.attributes['root-layout'];
    },
    showRootElement(state, getters) {
        return getters.rootLayout !== 'none';
    },
    rootId(state, getters) {
        return getters.courseware?.relationships?.root?.data?.id;
    },
    currentElement(state) {
        return state.currentElement;
    },
    currentStructuralElement(state, getters, rootState, rootGetters) {
        const id = getters.currentElement;
        return rootGetters['courseware-structural-elements/byId']({ id });
    },
    currentUnit(state, getters, rootState, rootGetters) {
        const id = getters.currentStructuralElement.relationships?.unit?.data?.id;
        if (id) {
            return rootGetters['courseware-units/byId']({ id });
        }
        return null;
    },
    currentElementBlocked(state, getters, rootState, rootGetters) {
        const elemData = getters.currentStructuralElement?.relationships?.['edit-blocker']?.data;
        return elemData !== null && elemData !== '' && getters.currentStructuralElement;
    },
    currentElementBlockerId(state, getters) {
        return getters.currentElementBlocked ? getters.currentStructuralElement?.relationships?.['edit-blocker']?.data?.id : null;
    },
    currentElementBlockedByThisUser(state, getters) {
        return getters.currentElementBlocked && getters.userId === getters.currentElementBlockerId;
    },
    currentElementBlockedByAnotherUser(state, getters) {
        return getters.currentElementBlocked && getters.userId !== getters.currentElementBlockerId;
    },
    currentElementisLink(state, getters, rootState, rootGetters) {
        return getters.currentStructuralElement?.attributes?.['is-link'] === 1;
    },
    currentStructuralElementImageURL(state, getters) {
        return getters.currentStructuralElement?.relationships?.image?.meta?.['download-url'];
    },
    licenses(state) {
        return state.licenses;
    },
    context(state) {
        return state.context;
    },
    blockTypes(state) {
        return state.courseware?.attributes?.['block-types'] ?? [];
    },
    containerTypes(state) {
        return state.courseware?.attributes?.['container-types'] ?? [];
    },
    favoriteBlockTypes(state) {
        const allBlockTypes = state.courseware?.attributes?.['block-types'] ?? [];
        const favorites = state.courseware?.attributes?.['favorite-block-types'] ?? [];

        return allBlockTypes.filter(({ type }) => favorites.includes(type));
    },
    viewMode(state) {
        return state.viewMode;
    },
    dashboardViewMode(state) {
        return state.dashboardViewMode;
    },
    showToolbar(state) {
        return state.showToolbar;
    },
    selectedToolbarItem(state) {
        return state.selectedToolbarItem;
    },
    blockAdder(state) {
        return state.blockAdder;
    },
    containerAdder(state) {
        return state.containerAdder;
    },
    showCompanionOverlay(state) {
        return state.showCompanionOverlay;
    },
    msgCompanionOverlay(state) {
        return state.msgCompanionOverlay;
    },
    styleCompanionOverlay(state) {
        return state.styleCompanionOverlay;
    },
    consumeMode(state) {
        return state.consumeMode;
    },
    httpClient(state) {
        return state.httpClient;
    },
    urlHelper(state) {
        return state.urlHelper;
    },
    userId(state) {
        return state.userId;
    },
    userIsTeacherInCourse(state) {
        return state.userIsTeacherInCourse;
    },
    userIsTeacher(state, getters) {
        if (getters.courseware.attributes === undefined) {
            return getters.userIsTeacherInCourse;
        }

        return getters.courseware.attributes['is-teacher'];
    },
    pluginManager(state) {
        return state.pluginManager;
    },
    showStructuralElementEditDialog(state) {
        return state.showStructuralElementEditDialog;
    },
    showStructuralElementAddDialog(state) {
        return state.showStructuralElementAddDialog;
    },
    showStructuralElementAddChooserDialog(state) {
        return state.showStructuralElementAddChooserDialog;
    },
    showStructuralElementCopyDialog(state) {
        return state.showStructuralElementCopyDialog;
    },
    showStructuralElementLinkDialog(state) {
        return state.showStructuralElementLinkDialog;
    },
    showStructuralElementImportDialog(state) {
        return state.showStructuralElementImportDialog;
    },
    showStructuralElementExportDialog(state) {
        return state.showStructuralElementExportDialog;
    },
    showStructuralElementExportChooserDialog(state) {
        return state.showStructuralElementExportChooserDialog;
    },
    showStructuralElementPdfExportDialog(state) {
        return state.showStructuralElementPdfExportDialog;
    },
    showStructuralElementInfoDialog(state) {
        return state.showStructuralElementInfoDialog;
    },
    showStructuralElementOerDialog(state) {
        return state.showStructuralElementOerDialog;
    },
    showStructuralElementDeleteDialog(state) {
        return state.showStructuralElementDeleteDialog;
    },
    showStructuralElementPublicLinkDialog(state) {
        return state.showStructuralElementPublicLinkDialog;
    },
    showStructuralElementRemoveLockDialog(state) {
        return state.showStructuralElementRemoveLockDialog;
    },
    showStructuralElementFeedbackDialog(state) {
        return state.showStructuralElementFeedbackDialog;
    },
    showStructuralElementFeedbackCreateDialog(state) {
        return state.showStructuralElementFeedbackCreateDialog;
    },
    showOverviewElementAddDialog(state) {
        return state.showOverviewElementAddDialog;
    },
    showSuggestOerDialog(state) {
        return state.showSuggestOerDialog;
    },
    importFilesState(state) {
        return state.importFilesState;
    },
    importFilesProgress(state) {
        return state.importFilesProgress;
    },
    importStructuresState(state) {
        return state.importStructuresState;
    },
    importStructuresProgress(state) {
        return state.importStructuresProgress;
    },
    importErrors(state) {
        return state.importErrors;
    },
    exportState(state) {
        return state.exportState;
    },
    exportProgress(state) {
        return state.exportProgress;
    },
    permissionFilter(state) {
        return state.permissionFilter;
    },
    purposeFilter(state) {
        return state.purposeFilter;
    },
    sourceFilter(state) {
        return state.sourceFilter;
    },
    bookmarkFilter(state) {
        return state.bookmarkFilter;
    },
    showSearchResults(state) {
        return state.showSearchResults;
    },
    searchResults(state) {
        return state.searchResults;
    },
    assistiveLiveContents(state) {
        return state.assistiveLiveContents;
    },
    progresses(state) {
        return state.progresses;
    },
    processing(state) {
        return state.processing;
    },

    oerCampusEnabled(state, getters, rootState, rootGetters) {
        return rootGetters['studip-properties/byId']({ id: 'oer-campus-enabled'}).attributes?.value;
    },
    oerEnableSuggestions(state, getters, rootState, rootGetters) {
        return getters.oerCampusEnabled && rootGetters['studip-properties/byId']({ id: 'oer-enable-suggestions'}).attributes?.value;
    },

    toolbarActive(state) {
        return state.toolbarActive;
    },
    hideEditLayout(state) {
        return state.hideEditLayout;
    },
    feedbackSettings(state) {
        return state.feedbackSettings;
    },
    isFeedbackActivated(state, getters) {
        return getters.feedbackSettings?.activated ?? false;
    },
    canCreateFeedbackElement(state, getters) {
        return getters.feedbackSettings?.createPerm ?? false;
    },
    canEditFeedbackElement(state, getters) {
        return getters.feedbackSettings?.adminPerm ?? false;
    },
};

export const state = { ...initialState };

export const actions = {
    loadContainer({ dispatch }, containerId) {
        const options = {
            include: 'blocks,blocks.edit-blocker','fields[users]': 'formatted-name',
        };

        return dispatch('courseware-containers/loadById', { id: containerId, options }, { root: true });
    },

    loadStructuralElement({ dispatch }, structuralElementId) {
        const options = {
            include:
                'containers,containers.edit-blocker,containers.blocks,containers.blocks.editor,containers.blocks.owner,containers.blocks.user-data-field,containers.blocks.user-progress,containers.blocks.edit-blocker,editor,edit-blocker,owner',
            'fields[users]': 'formatted-name',
        };

        return dispatch(
            'courseware-structural-elements/loadById',
            { id: structuralElementId, options },
            { root: true }
        );
    },

    loadFileRefs({ dispatch, rootGetters }, block_id) {
        const parent = {
            type: 'courseware-blocks',
            id: block_id,
        };
        const relationship = 'file-refs';

        return dispatch('file-refs/loadRelated', { parent, relationship }, { root: true }).then(() =>
            rootGetters['file-refs/related']({
                parent,
                relationship,
            })
        );
    },

    async loadUserDataFields({ dispatch }, blockId) {
        const parent = { type: 'courseware-blocks', id: `${blockId}` };
        const relationship = 'user-data-field';
        const options = {
            include: 'user',
        };

        return dispatch('user-data-field/loadRelated', { parent, relationship, options }, { root: true });
    },

    async loadCoursewareActivities({ dispatch, rootGetters }, { userId, courseId }) {
        const parent = {
            type: 'users',
            id: userId,
        };
        const relationship = 'activitystream';

        const options = {
            'filter[context-type]': 'course',
            'filter[context-id]': courseId,
            'filter[object-type]': 'courseware',
            include: 'actor, context, object',
        };

        await dispatch('users/loadRelated', { parent, relationship, options }, { root: true });

        const activities = rootGetters['users/all'];

        return activities.filter(({ type }) => type === 'activities');
    },

    async createFile(context, { file, filedata, folder }) {
        const termId = file?.relationships?.['terms-of-use']?.data?.id ?? null;
        const formData = new FormData();
        formData.append('file', filedata, file.attributes.name);
        if (termId) {
            formData.append('term-id', termId);
        }
        const url = `folders/${folder.id}/file-refs`;
        let request = await state.httpClient.post(url, formData, {
            headers: {
                'Content-Type': 'multipart/form-data',
            },
        });
        let response = null;
        try {
            response = await state.httpClient.get(request.headers.location);
        }
        catch(e) {
            console.debug(e);
            response = null;
        }

        return response ? response.data.data : response;
    },

    async updateFileContent(context, { file, filedata }) {
        const url = `file-refs/${file.id}/content`;
        const formData = new FormData();
        formData.append('file', filedata, file.attributes.name);
        let request = await state.httpClient.post(url, formData);
        let response = null;
        try {
            response = await state.httpClient.get(request.headers.location);
        }
        catch(e) {
            console.debug(e);
            response = null;
        }

        return response ? response.data.data : response;
    },

    async createRootFolder({ dispatch, rootGetters }, { context, folder }) {
        // get root folder for this context
        await dispatch(
            `${context.type}/loadRelated`,
            {
                parent: context,
                relationship: 'folders',
            },
            { root: true }
        );

        let folders = await rootGetters[`${context.type}/related`]({
            parent: context,
            relationship: 'folders',
        });

        let rootFolder = null;

        for (let i = 0; i < folders.length; i++) {
            if (folders[i].attributes['folder-type'] === 'RootFolder') {
                rootFolder = folders[i];
            }
        }

        const newFolder = {
            data: {
                type: 'folders',
                attributes: {
                    name: folder.name,
                    'folder-type': 'StandardFolder',
                },
                relationships: {
                    parent: {
                        data: {
                            type: 'folders',
                            id: rootFolder.id,
                        },
                    },
                },
            },
        };

        return state.httpClient.post(`${context.type}/${context.id}/folders`, newFolder).then((response) => {
            return response.data.data;
        });
    },

    async createFolder(store, { context, parent, folder }) {
        const newFolder = {
            data: {
                type: 'folders',
                attributes: {
                    name: folder.name,
                    'folder-type': folder.type,
                },
                relationships: {
                    parent: parent,
                },
            },
        };

        return state.httpClient.post(`${context.type}/${context.id}/folders`, newFolder).then((response) => {
            return response.data.data;
        });
    },

    loadFolder({ dispatch }, folderId) {
        const options = {};

        return dispatch('folders/loadById', { id: folderId, options }, { root: true });
    },

    copyBlock({ getters }, { parentId, block, section }) {
        const copy = {
            data: {
                block: block,
                parent_id: parentId,
                section: section
            },
        };

        return state.httpClient.post(`courseware-blocks/${block.id}/copy`, copy).then((resp) => {
            // console.log(resp);
        });
    },
    clipboardInsertBlock({ getters }, { parentId, clipboard, section }) {
        const insert = {
            data: {
                parent_id: parentId,
                section: section
            },
        };

        return state.httpClient.post(`courseware-clipboards/${clipboard.id}/insert`, insert);
    },
    copyContainer({ getters }, { parentId, container }) {
        const copy = {
            data: {
                container: container,
                parent_id: parentId,
            },
        };

        return state.httpClient.post(`courseware-containers/${container.id}/copy`, copy).then((resp) => {
            // console.log(resp);
        });
    },
    clipboardInsertContainer({ getters },{ parentId, clipboard }) {
        const insert = {
            data: {
                parent_id: parentId,
            },
        };

        return state.httpClient.post(`courseware-clipboards/${clipboard.id}/insert`, insert);
    },
    async copyStructuralElement({ dispatch, getters, rootGetters }, { parentId, elementId, removePurpose, migrate, modifications }) {
        const copy = { data: { parent_id: parentId, remove_purpose: removePurpose, migrate: migrate, modifications: modifications } };

        const result = await state.httpClient.post(`courseware-structural-elements/${elementId}/copy`, copy);
        const id = result.data.data.id;
        await dispatch('loadStructuralElement', id);

        const newElement = rootGetters['courseware-structural-elements/byId']({ id });

        return dispatch('courseware-structure/loadDescendants', { root: newElement });
    },

    async linkStructuralElement({ dispatch, getters, rootGetters }, { parentId, elementId }) {
        const link = { data: { parent_id: parentId } };

        const result = await state.httpClient.post(`courseware-structural-elements/${elementId}/link`, link);
        const id = result.data.data.id;
        await dispatch('loadStructuralElement', id);

        const newElement = rootGetters['courseware-structural-elements/byId']({ id });

        return dispatch('courseware-structure/loadDescendants', { root: newElement });

    },

    async activateStructuralElementComments({ dispatch }, { element }) {

        element.attributes.commentable = true;

        const updatedElement =  await dispatch('setStructuralElementComments', { element: element });

        return updatedElement;

    },
    async deactivateStructuralElementComments({ dispatch }, { element }) {

        element.attributes.commentable = false;

        const updatedElement =  await dispatch('setStructuralElementComments', { element: element });

        return updatedElement;
    },

    async setStructuralElementComments({ dispatch }, { element }) {
        await dispatch('lockObject', { id: element.id, type: 'courseware-structural-elements' });
        const updatedElement =  await dispatch('courseware-structural-elements/update', element, { root: true });
        await dispatch('unlockObject', { id: element.id, type: 'courseware-structural-elements' });

        return updatedElement;
    },

    async createBlockInContainer({ dispatch }, { container, blockType }) {
        const block = {
            attributes: {
                'block-type': blockType,
                payload: null,
            },
            relationships: {
                container: {
                    data: { type: container.type, id: container.id },
                },
            },
        };
        await dispatch('courseware-blocks/create', block, { root: true });

        return dispatch('loadContainer', container.id);
    },

    async deleteBlockInContainer({ dispatch }, { containerId, blockId }) {
        const data = {
            id: blockId,
        };
        await dispatch('courseware-blocks/delete', data, { root: true });
        //TODO throws TypeError: block is undefined after delete
        return dispatch('loadContainer', containerId);
    },

    async updateBlockInContainer({ dispatch }, { attributes, blockId, containerId }) {
        const container = {
            type: 'courseware-containers',
            id: containerId,
        };
        const block = {
            type: 'courseware-blocks',
            attributes: attributes,
            id: blockId,
            relationships: {
                container: {
                    data: { type: container.type, id: container.id },
                },
            },
        };

        await dispatch('courseware-blocks/update', block, { root: true });
        await dispatch('unlockObject', { id: blockId, type: 'courseware-blocks' });

        return dispatch('loadContainer', containerId);
    },

    async updateBlock({ dispatch }, { block, containerId }) {
        const container = {
            type: 'courseware-containers',
            id: containerId,
        };
        const updateBlock = {
            type: 'courseware-blocks',
            attributes: block.attributes,
            id: block.id,
            relationships: {
                container: {
                    data: { type: container.type, id: container.id },
                },
            },
        };
        await dispatch('courseware-blocks/update', updateBlock, { root: true });

        return dispatch('loadContainer', containerId);
    },

    async deleteBlock({ dispatch }, { containerId, blockId }) {
        const data = {
            id: blockId,
        };
        await dispatch('courseware-blocks/delete', data, { root: true });
        //TODO throws TypeError: block is undefined after delete
        return dispatch('loadContainer', containerId);
    },

    async activateBlockComments({ dispatch }, { block }) {

        block.attributes.commentable = true;

        const updatedBlock =  await dispatch('setBlockComments', { block: block });

        return updatedBlock;

    },
    async deactivateBlockComments({ dispatch }, { block }) {

        block.attributes.commentable = false;

        const updatedBlock =  await dispatch('setBlockComments', { block: block });

        return updatedBlock;
    },

    async setBlockComments({ dispatch }, { block }) {
        await dispatch('lockObject', { id: block.id, type: 'courseware-blocks' });
        const updatedBlock =  await dispatch('courseware-blocks/update', block, { root: true });
        await dispatch('unlockObject', { id: block.id, type: 'courseware-blocks' });

        return updatedBlock;
    },

    async storeCoursewareSettings({ dispatch, getters },
                                  { permission, progression, certificateSettings, reminderSettings,
                                      resetProgressSettings }) {
        const courseware = getters.courseware;
        courseware.attributes['editing-permission-level'] = permission;
        courseware.attributes['sequential-progression'] = progression;
        courseware.attributes['certificate-settings'] = certificateSettings;
        courseware.attributes['reminder-settings'] = reminderSettings;
        courseware.attributes['reset-progress-settings'] = resetProgressSettings;

        return dispatch('courseware-instances/update', courseware, { root: true });
    },

    sortChildrenInStructualElements({ dispatch }, { parent, children }) {
        const childrenResourceIdentifiers = children.map(({ type, id }) => ({ type, id }));

        return dispatch(
            `courseware-structural-elements/setRelated`,
            {
                parent: { type: parent.type, id: parent.id },
                relationship: 'children',
                data: childrenResourceIdentifiers,
            },
            { root: true }
        ).then(() => dispatch(
            `${parent.type}/loadRelated`,
            {
                parent: { type: parent.type, id: parent.id },
                relationship: 'children',
            },
            { root: true }
        )).then(() => dispatch('courseware-structure/build', null, { root: true }));
    },

    async createStructuralElement({ dispatch }, { attributes, parentId, currentId }) {
        const data = {
            attributes,
            relationships: {
                parent: {
                    data: {
                        type: 'courseware-structural-elements',
                        id: parentId,
                    },
                },
            },
        };
        await dispatch('courseware-structural-elements/create', data, { root: true });

        return dispatch('loadStructuralElement', currentId);
    },

    async createStructuralElementWithTemplate({ dispatch }, { attributes, parentId, currentId, templateId }) {
        const data = {
            attributes,
            relationships: {
                parent: {
                    data: {
                        type: 'courseware-structural-elements',
                        id: parentId,
                    },
                },
            },
            templateId: templateId,
        };
        await dispatch('courseware-structural-elements/create', data, { root: true });

        const options = {
            include: 'children',
        };

        return dispatch('courseware-structural-elements/loadById', { id: currentId, options }, { root: true });
    },

    async deleteStructuralElement({ dispatch }, { id, parentId }) {
        const data = {
            id: id,
        };
        await dispatch('courseware-structural-elements/delete', data, { root: true });
        return dispatch('loadStructuralElement', parentId);
    },

    async updateStructuralElement({ dispatch }, { element, id }) {
        await dispatch('courseware-structural-elements/update', element, { root: true });

        return dispatch('loadStructuralElement', id);
    },

    sortContainersInStructualElements({ dispatch }, { structuralElement, containers }) {
        const containerResourceIdentifiers = containers.map(({ type, id }) => ({ type, id }));

        return dispatch(
            `courseware-structural-elements/setRelated`,
            {
                parent: { type: structuralElement.type, id: structuralElement.id },
                relationship: 'containers',
                data: containerResourceIdentifiers,
            },
            { root: true }
        );
    },

    async createContainer({ dispatch }, { attributes, structuralElementId }) {
        const data = {
            attributes,
            relationships: {
                'structural-element': {
                    data: {
                        type: 'courseware-structural-elements',
                        id: structuralElementId,
                    },
                },
            },
        };
        await dispatch('courseware-containers/create', data, { root: true });

        return dispatch('loadStructuralElement', structuralElementId);
    },

    async deleteContainer({ dispatch }, { containerId, structuralElementId }) {
        const data = {
            id: containerId,
        };
        await dispatch('courseware-containers/delete', data, { root: true });
        //TODO throws TypeError: container is undefined after delete
        return dispatch('loadStructuralElement', structuralElementId);
    },

    async updateContainer({ dispatch }, { container, structuralElementId }) {
        await dispatch('courseware-containers/update', container, { root: true });

        return dispatch('loadStructuralElement', structuralElementId);
    },

    sortBlocksInContainer({ dispatch }, { container, sections }) {
        let blockResourceIdentifiers = [];

        sections.forEach((section) => {
            blockResourceIdentifiers.push(...section.blocks.map(({ type, id }) => ({ type, id })));
        });

        return dispatch(
            `courseware-containers/setRelated`,
            {
                parent: { type: container.type, id: container.id },
                relationship: 'blocks',
                data: blockResourceIdentifiers,
            },
            { root: true }
        );
    },

    lockObject({ dispatch, getters }, { id, type }) {
        return dispatch(`${type}/setRelated`, {
            parent: { id, type },
            relationship: 'edit-blocker',
            data: {
                type: 'users',
                id: getters.userId,
            },
        });
    },

    unlockObject({ dispatch }, { id, type }) {
        return dispatch(`${type}/setRelated`, {
            parent: { id, type },
            relationship: 'edit-blocker',
            data: null,
        });
    },

    async companionInfo({ dispatch }, { info }) {
        STUDIP.eventBus.emit('push-system-notification', { type: 'info', message: info});
    },

    async companionSuccess({ dispatch }, { info }) {
        STUDIP.eventBus.emit('push-system-notification', { type: 'success', message: info});
    },

    async companionError({ dispatch }, { info }) {
        STUDIP.eventBus.emit('push-system-notification', { type: 'error', message: info});
    },

    async companionWarning({ dispatch }, { info }) {
        STUDIP.eventBus.emit('push-system-notification', { type: 'exception', message: info});
    },

    async companionSpecial({ dispatch }, { info }) {
        STUDIP.eventBus.emit('push-system-notification', { type: 'warning', message: info});
    },

    // adds a favorite block type using the `type` of the BlockType
    async addFavoriteBlockType({ dispatch, getters }, blockType) {
        const blockTypes = new Set(getters.favoriteBlockTypes.map(({ type }) => type));
        blockTypes.add(blockType);

        return dispatch('storeFavoriteBlockTypes', [...blockTypes]);
    },

    // removes a favorite block type using the `type` of the BlockType
    async removeFavoriteBlockType({ dispatch, getters }, blockType) {
        const blockTypes = new Set(getters.favoriteBlockTypes.map(({ type }) => type));
        blockTypes.delete(blockType);

        return dispatch('storeFavoriteBlockTypes', [...blockTypes]);
    },

    // sets the favorite block types using an array of the `type`s of those BlockTypes
    async storeFavoriteBlockTypes({ dispatch, getters }, favoriteBlockTypes) {
        const courseware = getters.courseware;
        courseware.attributes['favorite-block-types'] = favoriteBlockTypes;

        return dispatch('courseware-instances/update', courseware, { root: true });
    },

    coursewareCurrentElement(context, id) {
        context.commit('coursewareCurrentElementSet', id);
    },

    coursewareContext(context, id) {
        context.commit('coursewareContextSet', id);
    },

    licenses(context, licenses) {
        context.commit('licensesSet', licenses);
    },

    coursewareViewMode(context, view) {
        context.commit('coursewareViewModeSet', view);
    },

    setDashboardViewMode(context, view) {
        context.commit('setDashboardViewMode', view);
    },

    coursewareShowToolbar(context, toolbar) {
        context.commit('coursewareShowToolbarSet', toolbar);
    },

    coursewareSelectedToolbarItem(context, item) {
        context.commit('coursewareSelectedToolbarItemSet', item);
    },

    coursewareBlockAdder(context, adder) {
        context.commit('coursewareBlockAdderSet', adder);
    },

    coursewareContainerAdder(context, adder) {
        context.commit('coursewareContainerAdderSet', adder);
    },

    coursewareShowCompanionOverlay(context, companion_overlay) {
        context.commit('coursewareShowCompanionOverlaySet', companion_overlay);
    },

    coursewareMsgCompanionOverlay(context, companion_overlay_msg) {
        context.commit('coursewareMsgCompanionOverlaySet', companion_overlay_msg);
    },

    coursewareStyleCompanionOverlay(context, companion_overlay_style) {
        context.commit('coursewareStyleCompanionOverlaySet', companion_overlay_style);
    },

    coursewareConsumeMode(context, mode) {
        context.commit('coursewareConsumeModeSet', mode);
    },
    setHttpClient({ commit }, httpClient) {
        commit('setHttpClient', httpClient);
    },

    setUrlHelper({ commit }, urlHelper) {
        commit('setUrlHelper', urlHelper);
    },

    setUserId({ commit }, userId) {
        commit('setUserId', userId);
    },

    showElementEditDialog(context, bool) {
        context.commit('setShowStructuralElementEditDialog', bool);
    },

    showElementAddDialog(context, bool) {
        context.commit('setShowStructuralElementAddDialog', bool);
    },

    showElementAddChooserDialog(context, bool) {
        context.commit('setShowStructuralElementAddChooserDialog', bool);
    },

    showElementImportDialog(context, bool) {
        context.commit('setShowStructuralElementImportDialog', bool);
    },

    showElementCopyDialog(context, bool) {
        context.commit('setShowStructuralElementCopyDialog', bool);
    },

    showElementLinkDialog(context, bool) {
        context.commit('setShowStructuralElementLinkDialog', bool);
    },

    showElementExportDialog(context, bool) {
        context.commit('setShowStructuralElementExportDialog', bool);
    },

    showElementExportChooserDialog(context, bool) {
        context.commit('setShowStructuralElementExportChooserDialog', bool);
    },

    showElementPdfExportDialog(context, bool) {
        context.commit('setShowStructuralElementPdfExportDialog', bool);
    },

    showElementInfoDialog(context, bool) {
        context.commit('setShowStructuralElementInfoDialog', bool);
    },

    showElementOerDialog(context, bool) {
        context.commit('setShowStructuralElementOerDialog', bool);
    },

    updateShowSuggestOerDialog(context, bool) {
        context.commit('setShowSuggestOerDialog', bool);
    },

    showElementDeleteDialog(context, bool) {
        context.commit('setShowStructuralElementDeleteDialog', bool);
    },

    showElementPublicLinkDialog(context, bool) {
        context.commit('setShowStructuralElementPublicLinkDialog', bool);
    },

    showElementRemoveLockDialog(context, bool) {
        context.commit('setShowStructuralElementRemoveLockDialog', bool);
    },

    showStructuralElementFeedbackDialog(context, bool) {
        context.commit('setShowStructuralElementFeedbackDialog', bool);
    },
    showStructuralElementFeedbackCreateDialog(context, bool) {
        context.commit('setShowStructuralElementFeedbackCreateDialog', bool);
    },

    setShowOverviewElementAddDialog(context, bool) {
        context.commit('setShowOverviewElementAddDialog', bool);
    },

    setImportFilesState({ commit }, state) {
        commit('setImportFilesState', state);
    },
    setImportFilesProgress({ commit }, percent) {
        commit('setImportFilesProgress', percent);
    },
    setImportStructuresState({ commit }, state) {
        commit('setImportStructuresState', state);
    },
    setImportStructuresProgress({ commit }, percent) {
        commit('setImportStructuresProgress', percent);
    },
    setImportErrors({ commit }, errors) {
        commit('setImportErrors', errors);
    },

    setExportState({ commit }, state) {
        commit('setExportState', state);
    },
    setExportProgress({ commit }, percent) {
        commit('setExportProgress', percent);
    },

    setShowSearchResults({ commit }, state) {
        commit('setShowSearchResults', state);
    },

    setSearchResults({ commit }, state) {
        commit('setSearchResults', state);
    },
    setAssistiveLiveContents({ commit }, state) {
        commit('setAssistiveLiveContents', state);
    },

    setUserIsTeacherInCourse({ commit }, state) {
        commit('setUserIsTeacherInCourse', state);
    },

    addBookmark({ dispatch, rootGetters }, structuralElement) {
        const cw = rootGetters['courseware'];

        // get existing bookmarks
        const bookmarks =
            rootGetters['courseware-structural-elements/related']({
                parent: cw,
                relationship: 'bookmarks',
            })?.map(({ type, id }) => ({ type, id })) ?? [];

        // add a new bookmark
        const data = [...bookmarks, { type: structuralElement.type, id: structuralElement.id }];

        // send them home
        return dispatch(
            `courseware-structural-elements/setRelated`,
            {
                parent: { type: cw.type, id: cw.id },
                relationship: 'bookmarks',
                data,
            },
            { root: true }
        );
    },

    removeBookmark({ dispatch, rootGetters }, structuralElement) {
        const cw = rootGetters['courseware'];

        // get existing bookmarks
        const bookmarks =
            rootGetters['courseware-structural-elements/related']({
                parent: cw,
                relationship: 'bookmarks',
            })?.map(({ type, id }) => ({ type, id })) ?? [];

        // filter bookmark that must be removed
        const data = bookmarks.filter(({ id }) => id !== structuralElement.id);

        // send them home
        return dispatch(
            `courseware-structural-elements/setRelated`,
            {
                parent: { type: cw.type, id: cw.id },
                relationship: 'bookmarks',
                data,
            },
            { root: true }
        );
    },

    setPluginManager({ commit }, pluginManager) {
        commit('setPluginManager', pluginManager);
    },

    uploadImageForStructuralElement({ dispatch, state }, { structuralElement, file }) {
        const formData = new FormData();
        formData.append('image', file);

        const url = `courseware-structural-elements/${structuralElement.id}/image`;
        return state.httpClient.post(url, formData, {
            headers: {
                'Content-Type': 'multipart/form-data',
            },
        });
    },

    setStockImageForStructuralElement({ dispatch, state }, { structuralElement, stockImage }) {
        const { id, type } = structuralElement;
        structuralElement.relationships.image = { data: { type: 'stock-images', id: stockImage.id } };

        return dispatch('lockObject', { id, type })
            .then(() => dispatch('updateStructuralElement', { element: structuralElement, id }))
            .then(() => dispatch('lockObject', { id, type }));
    },

    async deleteImageForStructuralElement({ dispatch, state }, structuralElement) {
        const url = `courseware-structural-elements/${structuralElement.id}/image`;
        await state.httpClient.delete(url);

        return dispatch('loadStructuralElement', structuralElement.id);
    },

    async loadRelatedPaginated({ dispatch, rootGetters }, { type, parent, relationship, options }) {
        const limit = 100;
        let offset = 0;

        await loadPage(offset, limit);
        const total = rootGetters[`${type}/lastMeta`].page.total;
        const pages = [];
        for (let page = 1; page * limit < total; page++) {
            pages.push(loadPage(page * limit, limit));
        }

        return Promise.all(pages);

        function loadPage(offset, limit) {
            return dispatch(
                `${type}/loadRelated`,
                {
                    parent,
                    relationship,
                    options: {
                        ...options,
                        'page[offset]': offset,
                        'page[limit]': limit,
                    },
                    resetRelated: false,
                },
                { root: true }
            )
        }
    },

    loadUsersBookmarks({ dispatch, rootGetters, state }, userId) {
        const parent = {
            type: 'users',
            id: userId,
        };
        const relationship = 'courseware-bookmarks';
        const options = {
            include: 'course',
        };

        return dispatch('loadRelatedPaginated', {
            type: 'courseware-structural-elements',
            parent,
            relationship,
            options,
        });
    },

    async loadUsersCourses({ dispatch, rootGetters, state }, { userId, withCourseware }) {
        const parent = {
            type: 'users',
            id: userId,
        };
        const relationship = 'course-memberships';
        const options = {
            include: 'course',
        };
        await dispatch('loadRelatedPaginated', {
            type: 'course-memberships',
            parent,
            relationship,
            options,
        });

        const memberships = rootGetters['course-memberships/related']({
            parent,
            relationship,
        });

        const otherMemberships = memberships.filter(({ attributes, relationships }) => {
            return ['dozent', 'tutor'].includes(attributes.permission);
        });

        if (!withCourseware) {
            return otherMemberships.map((membership) => {
                return getCourse(membership);
            });
        }

        const items = otherMemberships.map((membership) => {
            let course = getCourse(membership);
            course['userPermission'] = membership.attributes.permission;

            return { membership, course };
        });

         return items
            .filter(({ membership, course }) => {
                return course.relationships.courseware.data;
            })
            .map(({ course }) => course);

        function getCourse(membership) {
            return rootGetters['courses/related']({ parent: membership, relationship: 'course' });
        }
    },

    async loadRemoteCoursewareStructure({ dispatch, rootGetters }, { rangeId, rangeType }) {
        const parent = {
            id: rangeId,
            type: rangeType,
        };

        const relationship = 'courseware';

        return dispatch(`courseware-instances/loadRelated`, { parent, relationship }, { root: true }).then(
            (response) => {
                const instance = rootGetters['courseware-instances/related']({
                    parent: parent,
                    relationship: relationship,
                });

                return instance;
            },
            (error) => {
                return null;
            }
        );
    },

    loadFeedback({ dispatch }, blockId) {
        const parent = { type: 'courseware-blocks', id: `${blockId}` };
        const relationship = 'feedback';
        const options = {
            include: 'user',
        };

        return dispatch('courseware-block-feedback/loadRelated', { parent, relationship, options }, { root: true });
    },

    async createFeedback({ dispatch }, { blockId, feedback }) {
        const data = {
            attributes: {
                feedback,
            },
            relationships: {
                block: {
                    data: {
                        type: 'courseware-blocks',
                        id: blockId,
                    },
                },
            },
        };
        await dispatch('courseware-block-feedback/create', data, { root: true });

        return dispatch('loadFeedback', blockId);
    },

    async createTaskGroup({ dispatch, rootGetters }, { taskGroup }) {
        await dispatch('courseware-task-groups/create', taskGroup, { root: true });

        const id = taskGroup.relationships.target.data.id;
        const target = rootGetters['courseware-structural-elements/byId']({ id });

        return dispatch('courseware-structure/loadDescendants', { root: target });
    },

    async loadTask({ dispatch }, { taskId }) {
        return dispatch(
            'courseware-tasks/loadById',
            {
                id: taskId,
                options: {
                    include: 'solver,task-group,task-group.lecturer',
                },
            },
            { root: true }
        ).catch(error => console.debug(error));
    },

    async updateTask({ dispatch }, { attributes, taskId }) {
        const task = {
            type: 'courseware-tasks',
            attributes: attributes,
            id: taskId,
        };
        await dispatch('courseware-tasks/update', task, { root: true });

        return dispatch('loadTask', { taskId: task.id });
    },

    async deleteTask({ dispatch }, { task }) {
        const data = {
            id: task.id,
        };
        await dispatch('courseware-tasks/delete', data, { root: true });
    },

    async createTaskFeedback({ dispatch }, { taskFeedback }) {
        await dispatch('courseware-task-feedback/create', taskFeedback, { root: true });

        return dispatch('loadTask', { taskId: taskFeedback.relationships.task.data.id });
    },

    async updateTaskFeedback({ dispatch }, { attributes, taskFeedbackId }) {
        const taskFeedback = {
            type: 'courseware-task-feedback',
            attributes: attributes,
            id: taskFeedbackId,
        };
        await dispatch('courseware-task-feedback/update', taskFeedback, { root: true });

        return dispatch('courseware-task-feedback/loadById', { id: taskFeedback.id }, { root: true });
    },

    async deleteTaskFeedback({ dispatch }, { taskFeedbackId }) {
        const data = {
            id: taskFeedbackId,
        };
        await dispatch('courseware-task-feedback/delete', data, { root: true });
    },

    setPermissionFilter({ commit }, permission) {
        commit('setPermissionFilter', permission);
    },
    setPurposeFilter({ commit }, purpose) {
        commit('setPurposeFilter', purpose);
    },
    setSourceFilter({ commit }, source) {
        commit('setSourceFilter', source);
    },
    setBookmarkFilter({ commit }, course) {
        commit('setBookmarkFilter', course);
    },

    setProcessing({ commit }, processing) {
        commit('setProcessing', processing);
    },

    createLink({ dispatch, rootGetters }, { publicLink }) {
        dispatch('courseware-public-links/create', publicLink, { root: true });
    },

    deleteLink({ dispatch }, { linkId }) {
        const data = {
            id: linkId,
        };
        dispatch('courseware-public-links/delete', data, { root: true });
    },

    async updateLink({ dispatch }, { attributes, linkId }) {
        const link = {
            type: 'courseware-public-links',
            attributes: attributes,
            id: linkId,
        };
        await dispatch('courseware-public-links/update', link, { root: true });

        return dispatch('courseware-public-links/loadById', { id: link.id }, { root: true });
    },

    loadCourseUnits({ dispatch }, cid) {
        const parent = { type: 'courses', id: cid };
        const relationship = 'courseware-units';
        const options = { include: 'structural-element' }

        return dispatch('loadRelatedPaginated', {
            type: 'courseware-units',
            parent,
            relationship,
            options,
        });
    },
    loadUserUnits({ dispatch }, uid) {
        const parent = { type: 'users', id: uid };
        const relationship = 'courseware-units';
        const options = { include: 'structural-element' }

        return dispatch('loadRelatedPaginated', {
            type: 'courseware-units',
            parent,
            relationship,
            options,
        });
    },
    async loadUnitProgresses({ getters }, { unitId }) {
        const response = await state.httpClient.get(`courseware-units/${unitId}/courseware-user-progresses`);
        if (response.status === 200) {
           return response.data;
        } else {
           return null;
        }
    },
    async loadProgresses({ dispatch, commit, getters }) {
        const progresses = await dispatch('loadUnitProgresses', { unitId: getters.context.unit });
        commit('setProgresses', progresses);
    },

    loadUserClipboards({ dispatch }, uid) {
        dispatch('courseware-clipboards/resetState');
        const parent = { type: 'users', id: uid };
        const relationship = 'courseware-clipboards';
        const options = {}

        return dispatch('loadRelatedPaginated', {
            type: 'courseware-clipboards',
            parent,
            relationship,
            options,
        });
    },

    async deleteUserClipboards({ dispatch, rootGetters }, { uid, type }) {
        await state.httpClient.delete(`users/${uid}/courseware-clipboards/${type}`);
        dispatch('loadUserClipboards', uid);
    },

    toggleToolbarActive({ commit, rootGetters }) {
        if (!rootGetters['toolbarActive']) {
            commit('setHideEditLayout', false);
        }
        commit('setToolbarActive', !rootGetters['toolbarActive']);
    },
    toggleHideEditLayout({ commit, rootGetters}) {
        commit('setHideEditLayout', !rootGetters['hideEditLayout']);
    },
    setHideEditLayout({commit}, state) {
        commit('setHideEditLayout', state);
    },
    setFeedbackSettings(context, feedbackSettings) {
        context.commit('setFeedbackSettings', feedbackSettings);
    },
};

/* eslint no-param-reassign: ["error", { "props": false }] */
export const mutations = {
    coursewareSet(state, data) {
        state.courseware = data;
    },

    coursewareCurrentElementSet(state, data) {
        state.lastElement = state.currentElement;
        state.currentElement = data;
    },

    coursewareContextSet(state, data) {
        state.context = data;
    },

    licensesSet(state, data) {
        state.licenses = data;
    },

    coursewareViewModeSet(state, data) {
        state.viewMode = data;
    },

    setDashboardViewMode(state, data) {
        state.dashboardViewMode = data;
    },

    coursewareShowToolbarSet(state, data) {
        state.showToolbar = data;
    },

    coursewareSelectedToolbarItemSet(state, data) {
        state.selectedToolbarItem = data;
    },

    coursewareBlockAdderSet(state, data) {
        state.blockAdder = data;
    },

    coursewareContainerAdderSet(state, data) {
        state.containerAdder = data;
    },

    coursewareShowCompanionOverlaySet(state, data) {
        state.showCompanionOverlay = data;
    },

    coursewareMsgCompanionOverlaySet(state, data) {
        state.msgCompanionOverlay = data;
    },

    coursewareStyleCompanionOverlaySet(state, data) {
        state.styleCompanionOverlay = data;
    },

    coursewareConsumeModeSet(state, data) {
        state.consumeMode = data;
    },

    setHttpClient(state, httpClient) {
        state.httpClient = httpClient;
    },

    setUrlHelper(state, urlHelper) {
        state.urlHelper = urlHelper;
    },

    setUserId(state, userId) {
        state.userId = userId;
    },

    setPluginManager(state, pluginManager) {
        state.pluginManager = pluginManager;
    },

    setShowStructuralElementEditDialog(state, showEdit) {
        state.showStructuralElementEditDialog = showEdit;
    },

    setShowStructuralElementAddDialog(state, showAdd) {
        state.showStructuralElementAddDialog = showAdd;
    },

    setShowStructuralElementAddChooserDialog(state, showAddChooser) {
        state.showStructuralElementAddChooserDialog = showAddChooser;
    },

    setShowStructuralElementImportDialog(state, showImport) {
        state.showStructuralElementImportDialog = showImport;
    },

    setShowStructuralElementCopyDialog(state, showCopy) {
        state.showStructuralElementCopyDialog = showCopy;
    },

    setShowStructuralElementLinkDialog(state, showLink) {
        state.showStructuralElementLinkDialog = showLink;
    },

    setShowStructuralElementExportDialog(state, showExport) {
        state.showStructuralElementExportDialog = showExport;
    },

    setShowStructuralElementExportChooserDialog(state, showExportChooser) {
        state.showStructuralElementExportChooserDialog = showExportChooser;
    },

    setShowStructuralElementPdfExportDialog(state, showPdfExport) {
        state.showStructuralElementPdfExportDialog = showPdfExport;
    },

    setShowStructuralElementInfoDialog(state, showInfo) {
        state.showStructuralElementInfoDialog = showInfo;
    },

    setShowSuggestOerDialog(state, showSuggest) {
        state.showSuggestOerDialog = showSuggest;
    },

    setShowStructuralElementOerDialog(state, showOer) {
        state.showStructuralElementOerDialog = showOer;
    },

    setShowStructuralElementDeleteDialog(state, showDelete) {
        state.showStructuralElementDeleteDialog = showDelete;
    },

    setShowOverviewElementAddDialog(state, showAdd) {
        state.showOverviewElementAddDialog = showAdd;
    },

    setShowStructuralElementPublicLinkDialog(state, showPublicLink) {
        state.showStructuralElementPublicLinkDialog = showPublicLink;
    },

    setShowStructuralElementRemoveLockDialog(state, showRemoveLock) {
        state.showStructuralElementRemoveLockDialog = showRemoveLock;
    },

    setShowStructuralElementFeedbackDialog(state, showFeedback) {
        state.showStructuralElementFeedbackDialog = showFeedback;
    },
    setShowStructuralElementFeedbackCreateDialog(state, showFeedbackCreate) {
        state.showStructuralElementFeedbackCreateDialog = showFeedbackCreate;
    },

    setImportFilesState(state, importFilesState) {
        state.importFilesState = importFilesState;
    },

    setImportFilesProgress(state, importFilesProgress) {
        state.importFilesProgress = importFilesProgress;
    },
    setImportErrors(state, importErrors) {
        state.importErrors = importErrors;
    },

    setImportStructuresState(state, importStructuresState) {
        state.importStructuresState = importStructuresState;
    },

    setImportStructuresProgress(state, importStructuresProgress) {
        state.importStructuresProgress = importStructuresProgress;
    },

    setExportState(state, exportState) {
        state.exportState = exportState;
    },
    setExportProgress(state, exportProgress) {
        state.exportProgress = exportProgress;
    },
    setPermissionFilter(state, permission) {
        state.permissionFilter = permission;
    },
    setPurposeFilter(state, purpose) {
        state.purposeFilter = purpose;
    },
    setSourceFilter(state, source) {
        state.sourceFilter = source;
    },
    setBookmarkFilter(state, course) {
        state.bookmarkFilter = course;
    },
    setShowSearchResults(state, searchState) {
        state.showSearchResults = searchState;
    },
    setSearchResults(state, results) {
        state.searchResults = results;
    },
    setAssistiveLiveContents(state, text) {
        state.assistiveLiveContents = text;
    },
    setUserIsTeacherInCourse(state, isTeacher) {
        state.userIsTeacherInCourse = isTeacher;
    },
    setProgresses(state, data) {
        state.progresses = data;
    },
    setToolbarActive(state, active) {
        state.toolbarActive = active;
    },
    setHideEditLayout(state, hide) {
        state.hideEditLayout = hide;
    },
    setFeedbackSettings(state, feedbackSettings) {
        state.feedbackSettings = feedbackSettings;
    },
    setProcessing(state, processing) {
        state.processing = processing;
    }
};

export default {
    state,
    actions,
    mutations,
    getters,
};