Skip to content
Snippets Groups Projects
courseware.module.js 46.3 KiB
Newer Older
import axios from 'axios';

const getDefaultState = () => {
    return {
        blockAdder: {},
        containerAdder: false,
        consumeMode: false,
        context: {},
        courseware: {},
        currentElement: {},
Ron Lucke's avatar
Ron Lucke committed
        oerEnabled: null,
        licenses: null, // we need a route for License SORM
        httpClient: null,
        lastElement: null,
        msg: 'Dehydrated',
        msgCompanionOverlay:
            'Hallo! Ich bin Ihr persönlicher Companion. Wussten Sie schon, dass Courseware jetzt noch einfacher zu bedienen ist?',
        styleCompanionOverlay: 'default',
        pluginManager: null,
        showCompanionOverlay: false,
        showToolbar: false,
Ron Lucke's avatar
Ron Lucke committed
        selectedToolbarItem: 'contents',
        urlHelper: null,
        userId: null,
        viewMode: 'read',
Ron Lucke's avatar
Ron Lucke committed
        dashboardViewMode: 'default',
        filingData: {},
        userIsTeacher: false,
Ron Lucke's avatar
Ron Lucke committed
        teacherStatusLoaded: false,

        showStructuralElementEditDialog: false,
        showStructuralElementAddDialog: false,
        showStructuralElementExportDialog: false,
Ron Lucke's avatar
Ron Lucke committed
        showStructuralElementPdfExportDialog: false,
        showStructuralElementInfoDialog: false,
        showStructuralElementDeleteDialog: false,
        showStructuralElementOerDialog: false,
        showStructuralElementLinkDialog: false,
Ron Lucke's avatar
Ron Lucke committed
        showStructuralElementRemoveLockDialog: false,
        showSuggestOerDialog: false,

Ron Lucke's avatar
Ron Lucke committed
        structuralElementSortMode: false,

Ron Lucke's avatar
Ron Lucke committed
        importFilesState: '',
        importFilesProgress: 0,
        importStructuresState: '',
        importStructuresProgress: 0,
Ron Lucke's avatar
Ron Lucke committed
        importErrors: [],
Ron Lucke's avatar
Ron Lucke committed

        exportState: '',
        exportProgress: 0,
Ron Lucke's avatar
Ron Lucke committed

Ron Lucke's avatar
Ron Lucke committed
        purposeFilter: 'all',
Ron Lucke's avatar
Ron Lucke committed
        showOverviewElementAddDialog: false,

        bookmarkFilter: 'all',

        showSearchResults: false,
        searchResults: [],
    };
};

const initialState = getDefaultState();

const getters = {
    msg(state) {
        return state.msg;
    },
    lastElement(state) {
        return state.lastElement;
    },
    courseware(state) {
        return state.courseware;
    },
    currentElement(state) {
        return state.currentElement;
    },
Ron Lucke's avatar
Ron Lucke committed
    currentStructuralElement(state, getters, rootState, rootGetters) {
        const id = getters.currentElement;
        return rootGetters['courseware-structural-elements/byId']({ id });
    },
    currentElementBlocked(state, getters, rootState, rootGetters) {
        return getters.currentStructuralElement?.relationships?.['edit-blocker']?.data !== null;
    },
    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;
    },
Ron Lucke's avatar
Ron Lucke committed
    oerEnabled(state) {
        return state.oerEnabled;
    },
    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;
    },
Ron Lucke's avatar
Ron Lucke committed
    dashboardViewMode(state) {
        return state.dashboardViewMode;
    },
    showToolbar(state) {
        return state.showToolbar;
    },
Ron Lucke's avatar
Ron Lucke committed
    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;
    },
    userIsTeacher(state) {
        return state.userIsTeacher;
    },
Ron Lucke's avatar
Ron Lucke committed
    teacherStatusLoaded(state) {
        return state.teacherStatusLoaded;
    },
    pluginManager(state) {
        return state.pluginManager;
    },
    filingData(state) {
        return state.filingData;
    },
    showStructuralElementEditDialog(state) {
        return state.showStructuralElementEditDialog;
    },
    showStructuralElementAddDialog(state) {
        return state.showStructuralElementAddDialog;
    },
    showStructuralElementExportDialog(state) {
        return state.showStructuralElementExportDialog;
    },
Ron Lucke's avatar
Ron Lucke committed
    showStructuralElementPdfExportDialog(state) {
        return state.showStructuralElementPdfExportDialog;
    },
    showStructuralElementInfoDialog(state) {
        return state.showStructuralElementInfoDialog;
    },
    showStructuralElementOerDialog(state) {
        return state.showStructuralElementOerDialog;
    },
    showStructuralElementDeleteDialog(state) {
        return state.showStructuralElementDeleteDialog;
    showStructuralElementLinkDialog(state) {
        return state.showStructuralElementLinkDialog;
    },
Ron Lucke's avatar
Ron Lucke committed
    showStructuralElementRemoveLockDialog(state) {
        return state.showStructuralElementRemoveLockDialog;
    },
Ron Lucke's avatar
Ron Lucke committed
    showOverviewElementAddDialog(state) {
        return state.showOverviewElementAddDialog;
    },
    showSuggestOerDialog(state) {
        return state.showSuggestOerDialog;
    },
Ron Lucke's avatar
Ron Lucke committed
    structuralElementSortMode(state) {
        return state.structuralElementSortMode;
    },
Ron Lucke's avatar
Ron Lucke committed
    importFilesState(state) {
        return state.importFilesState;
    },
    importFilesProgress(state) {
        return state.importFilesProgress;
    },
    importStructuresState(state) {
        return state.importStructuresState;
    },
    importStructuresProgress(state) {
        return state.importStructuresProgress;
    },
Ron Lucke's avatar
Ron Lucke committed
    importErrors(state) {
        return state.importErrors;
    },
Ron Lucke's avatar
Ron Lucke committed
    exportState(state) {
        return state.exportState;
    },
    exportProgress(state) {
        return state.exportProgress;
    },
    permissionFilter(state) {
        return state.permissionFilter;
    },
Ron Lucke's avatar
Ron Lucke committed
    purposeFilter(state) {
        return state.purposeFilter;
    },
    sourceFilter(state) {
        return state.sourceFilter;
    },
Ron Lucke's avatar
Ron Lucke committed
    bookmarkFilter(state) {
        return state.bookmarkFilter;
    },
    showSearchResults(state) {
        return state.showSearchResults;
    },
    searchResults(state) {
        return state.searchResults;
    },
};

export const state = { ...initialState };

export const actions = {
    loadContainer({ dispatch }, containerId) {
        const options = {
Ron Lucke's avatar
Ron Lucke committed
            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:
Ron Lucke's avatar
Ron Lucke committed
                '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';

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

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

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

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

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

        const parentFetchers = activities
              .filter(({ type }) => type === 'activities')
              .map((activity) => this.dispatch(
                  'courseware-structural-elements/loadById',
                  {
                      id: activity.relationships.object.meta['object-id'],
                  },
              ));
Ron Lucke's avatar
Ron Lucke committed

        return Promise.all(parentFetchers).then(() => activities);
    },

    async createFile(context, { file, filedata, folder }) {
B. Sc Pius Gyamenah's avatar
B. Sc Pius Gyamenah committed
        const termId = file?.relationships['terms-of-use']?.data?.id ?? null;
        const formData = new FormData();
        formData.append('file', filedata, file.attributes.name);
Ron Lucke's avatar
Ron Lucke committed
        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',
            },
        });
Ron Lucke's avatar
Ron Lucke committed
        let response = null;
        try {
            response = await state.httpClient.get(request.headers.location);
        }
        catch(e) {
            console.debug(e);
            response = null;
        }
Ron Lucke's avatar
Ron Lucke committed
        return response ? response.data.data : response;
    },

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

Ron Lucke's avatar
Ron Lucke committed
        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,
                        },
                    },
                },
            },
        };

Ron Lucke's avatar
Ron Lucke committed
        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,
                },
            },
        };

Ron Lucke's avatar
Ron Lucke committed
        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 }) {
        const copy = {
            data: {
                block: block,
                parent_id: parentId,
            },
        };

        return state.httpClient.post(`courseware-blocks/${block.id}/copy`, copy).then((resp) => {
            // console.log(resp);
        });
    },
    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);
        });
    },
Ron Lucke's avatar
Ron Lucke committed
    async copyStructuralElement({ dispatch, getters, rootGetters }, { parentId, elementId, removePurpose, migrate }) {
        const copy = { data: { parent_id: parentId, remove_purpose: removePurpose, migrate: migrate } };
Ron Lucke's avatar
Ron Lucke committed
        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 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 storeCoursewareSettings({ dispatch, getters }, { permission, progression }) {
        const courseware = getters.courseware;
        courseware.attributes['editing-permission-level'] = permission;
        courseware.attributes['sequential-progression'] = progression;

        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);
    },

Ron Lucke's avatar
Ron Lucke committed
    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 }) {
        await dispatch('coursewareStyleCompanionOverlay', 'default');
        await dispatch('coursewareMsgCompanionOverlay', info);
        return dispatch('coursewareShowCompanionOverlay', true);
    },

    async companionSuccess({ dispatch }, { info }) {
        await dispatch('coursewareStyleCompanionOverlay', 'happy');
        await dispatch('coursewareMsgCompanionOverlay', info);
        return dispatch('coursewareShowCompanionOverlay', true);
    },

    async companionError({ dispatch }, { info }) {
        await dispatch('coursewareStyleCompanionOverlay', 'sad');
        await dispatch('coursewareMsgCompanionOverlay', info);
        return dispatch('coursewareShowCompanionOverlay', true);
    },

    async companionWarning({ dispatch }, { info }) {
        await dispatch('coursewareStyleCompanionOverlay', 'alert');
        await dispatch('coursewareMsgCompanionOverlay', info);
        return dispatch('coursewareShowCompanionOverlay', true);
    },

    async companionSpecial({ dispatch }, { info }) {
        await dispatch('coursewareStyleCompanionOverlay', 'special');
        await dispatch('coursewareMsgCompanionOverlay', info);
        return dispatch('coursewareShowCompanionOverlay', true);
    },

    // 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);
    },

Ron Lucke's avatar
Ron Lucke committed
    oerEnabled(context, enabled) {
        context.commit('oerEnabledSet', enabled);
    },

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

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

Ron Lucke's avatar
Ron Lucke committed
    setDashboardViewMode(context, view) {
        context.commit('setDashboardViewMode', view);
    },

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

Ron Lucke's avatar
Ron Lucke committed
    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) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementEditDialog', bool);
    },

    showElementAddDialog(context, bool) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementAddDialog', bool);
    },

    showElementExportDialog(context, bool) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementExportDialog', bool);
Ron Lucke's avatar
Ron Lucke committed
    showElementPdfExportDialog(context, bool) {
        context.commit('setShowStructuralElementPdfExportDialog', bool);
    },

    showElementInfoDialog(context, bool) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementInfoDialog', bool);
    },

    showElementOerDialog(context, bool) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementOerDialog', bool);
    updateShowSuggestOerDialog(context, bool) {
        context.commit('setShowSuggestOerDialog', bool);
    },

    showElementDeleteDialog(context, bool) {
Ron Lucke's avatar
Ron Lucke committed
        context.commit('setShowStructuralElementDeleteDialog', bool);
    showElementLinkDialog(context, bool) {
        context.commit('setShowStructuralElementLinkDialog', bool);
    },

Ron Lucke's avatar
Ron Lucke committed
    showElementRemoveLockDialog(context, bool) {
        context.commit('setShowStructuralElementRemoveLockDialog', bool);
    },

Ron Lucke's avatar
Ron Lucke committed
    setShowOverviewElementAddDialog(context, bool) {
        context.commit('setShowOverviewElementAddDialog', bool);
Ron Lucke's avatar
Ron Lucke committed

    setStructuralElementSortMode({ commit }, bool) {
        commit('setStructuralElementSortMode', bool);
Ron Lucke's avatar
Ron Lucke committed

    setImportFilesState({ commit }, state) {
        commit('setImportFilesState', state);
Ron Lucke's avatar
Ron Lucke committed
    setImportFilesProgress({ commit }, percent) {
        commit('setImportFilesProgress', percent);
Ron Lucke's avatar
Ron Lucke committed
    setImportStructuresState({ commit }, state) {
        commit('setImportStructuresState', state);
    },
    setImportStructuresProgress({ commit }, percent) {
        commit('setImportStructuresProgress', percent);
    },
    setImportErrors({ commit }, errors) {
Ron Lucke's avatar
Ron Lucke committed
        commit('setImportErrors', errors);
    },
Ron Lucke's avatar
Ron Lucke committed
    setExportState({ commit }, state) {
        commit('setExportState', state);
Ron Lucke's avatar
Ron Lucke committed
    setExportProgress({ commit }, percent) {
        commit('setExportProgress', percent);
    setShowSearchResults({ commit }, state) {
        commit('setShowSearchResults', state);
    },

    setSearchResults({ commit }, state) {
        commit('setSearchResults', 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',
            },
        });
    },

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

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

    cwManagerFilingData(context, msg) {
        context.commit('cwManagerFilingDataSet', msg);
    },

    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(