Commit 830c3eb9 authored by Ferran Recio Calderó's avatar Ferran Recio Calderó Committed by Amaia
Browse files

MDL-71209 courseformat: add course index modules

The course index is the first UI component that implements the new
drawers and the reactive components. The course index uses the course
state to present the current course structure and changes whenever
that structure change.
parent 804e138c
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
...@@ -48,6 +48,18 @@ function dispatchStateChangedEvent(detail, target) { ...@@ -48,6 +48,18 @@ function dispatchStateChangedEvent(detail, target) {
})); }));
} }
/**
* Setup the current view settings
*
* @param {number} courseId the course id
* @param {setup} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
*/
export const setViewFormat = (courseId, setup) => {
const editor = getCourseEditor(courseId);
editor.setViewFormat(setup);
};
/** /**
* Get a specific course editor reactive instance. * Get a specific course editor reactive instance.
* *
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
import {Reactive} from 'core/reactive'; import {Reactive} from 'core/reactive';
import notification from 'core/notification'; import notification from 'core/notification';
import Exporter from 'core_courseformat/local/courseeditor/exporter';
import log from 'core/log'; import log from 'core/log';
import ajax from 'core/ajax'; import ajax from 'core/ajax';
...@@ -44,6 +45,9 @@ export default class extends Reactive { ...@@ -44,6 +45,9 @@ export default class extends Reactive {
throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`); throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);
} }
// Default view format setup.
this._editing = false;
this.courseId = courseId; this.courseId = courseId;
let stateData; let stateData;
...@@ -56,13 +60,19 @@ export default class extends Reactive { ...@@ -56,13 +60,19 @@ export default class extends Reactive {
return; return;
} }
// Edit mode is part of the state but it could change over time.
// Components should use isEditing method to check the editing mode instead.
this._editing = stateData.course.editmode ?? false;
this.setInitialState(stateData); this.setInitialState(stateData);
} }
/**
* Setup the current view settings
*
* @param {Object} setup format, page and course settings
* @property {boolean} setup.editing if the page is in edit mode
*/
setViewFormat(setup) {
this._editing = setup.editing ?? false;
}
/** /**
* Load the current course state from the server. * Load the current course state from the server.
* *
...@@ -97,6 +107,15 @@ export default class extends Reactive { ...@@ -97,6 +107,15 @@ export default class extends Reactive {
return this._editing ?? false; return this._editing ?? false;
} }
/**
* Return a data exporter to transform state part into mustache contexts.
*
* @return {Exporter} the exporter class
*/
getExporter() {
return new Exporter(this);
}
/** /**
* Dispatch a change in the state. * Dispatch a change in the state.
* *
......
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to export parts of the state and transform them to be used in templates
* and as draggable data.
*
* @module core_courseformat/local/courseeditor/exporter
* @class core_courseformat/local/courseeditor/exporter
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class {
/**
* Class constructor.
*
* @param {CourseEditor} reactive the course editor object
*/
constructor(reactive) {
this.reactive = reactive;
}
/**
* Generate the course export data from the state.
*
* @param {Object} state the current state.
* @returns {Object}
*/
course(state) {
// Collect section information from the state.
const data = {
sections: [],
editmode: this.reactive.isEditing,
};
const sectionlist = state.course.sectionlist ?? [];
sectionlist.forEach(sectionid => {
const sectioninfo = state.section.get(sectionid) ?? {};
const section = this.section(state, sectioninfo);
data.sections.push(section);
});
data.hassections = (data.sections.length != 0);
return data;
}
/**
* Generate a section export data from the state.
*
* @param {Object} state the current state.
* @param {Object} sectioninfo the section state data.
* @returns {Object}
*/
section(state, sectioninfo) {
const section = {
...sectioninfo,
cms: [],
isactive: false,
};
const cmlist = sectioninfo.cmlist ?? [];
cmlist.forEach(cmid => {
const cminfo = state.cm.get(cmid);
const cm = this.cm(state, cminfo);
section.cms.push(cm);
});
section.hascms = (section.cms.length != 0);
return section;
}
/**
* Generate a cm export data from the state.
*
* @param {Object} state the current state.
* @param {Object} cminfo the course module state data.
* @returns {Object}
*/
cm(state, cminfo) {
const cm = {
...cminfo,
isactive: false,
};
return cm;
}
}
...@@ -51,61 +51,37 @@ export default class { ...@@ -51,61 +51,37 @@ export default class {
* Get updated state data related to some cm ids. * Get updated state data related to some cm ids.
* *
* @method cmState * @method cmState
* @param {StateManager} statemanager the current state * @param {StateManager} stateManager the current state
* @param {array} cmids the list of cm ids to update * @param {array} cmids the list of cm ids to update
*/ */
async cmState(statemanager, cmids) { async cmState(stateManager, cmids) {
const state = statemanager.state; const course = stateManager.get('course');
const updates = await this._callEditWebservice('cm_state', state.course.id, cmids); const updates = await this._callEditWebservice('cm_state', course.id, cmids);
statemanager.setReadOnly(false); stateManager.processUpdates(updates);
this._processUpdates(statemanager, updates);
} }
/** /**
* Get updated state data related to some section ids. * Get updated state data related to some section ids.
* *
* @method sectionState * @method sectionState
* @param {StateManager} statemanager the current state * @param {StateManager} stateManager the current state
* @param {array} sectionIds the list of section ids to update * @param {array} sectionIds the list of section ids to update
*/ */
async sectionState(statemanager, sectionIds) { async sectionState(stateManager, sectionIds) {
const state = statemanager.state; const course = stateManager.get('course');
const updates = await this._callEditWebservice('section_state', state.course.id, sectionIds); const updates = await this._callEditWebservice('section_state', course.id, sectionIds);
this._processUpdates(statemanager, updates); stateManager.processUpdates(updates);
} }
/** /**
* Helper to propcess both section_state and cm_state action results. * Get the full updated state data of the course.
* *
* @param {StateManager} statemanager the current state * @param {StateManager} stateManager the current state
* @param {Array} updates of updates. */
*/ async courseState(stateManager) {
_processUpdates(statemanager, updates) { const course = stateManager.get('course');
const updates = await this._callEditWebservice('course_state', course.id);
const state = statemanager.state; stateManager.processUpdates(updates);
statemanager.setReadOnly(false);
// The cm_state and section_state state action returns only updated states. However, most of the time we need this
// mutation to fix discrepancies between the course content and the course state because core_course_edit_module
// does not provide enough information to rebuild some state objects. This is the reason why we cannot use
// the batch method processUpdates as the rest of mutations do.
updates.forEach((update) => {
if (update.name === undefined) {
throw Error('Missing state update name');
}
// Compare the action with the current state.
let current = state[update.name];
if (current instanceof Map) {
current = state[update.name].get(update.fields.id);
}
if (!current) {
update.action = 'create';
}
statemanager.processUpdate(update.name, update.action, update.fields);
});
statemanager.setReadOnly(true);
} }
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment