Skip to content
Snippets Groups Projects
Commit e6a11cb9 authored by Marcus Eibrink-Lunzenauer's avatar Marcus Eibrink-Lunzenauer
Browse files

Marry course manager with the caching structure loading.

Closes #446.
parent c05ed50d
No related branches found
No related tags found
No related merge requests found
...@@ -26,24 +26,36 @@ class StructuralElementsCopy extends NonJsonApiController ...@@ -26,24 +26,36 @@ class StructuralElementsCopy extends NonJsonApiController
{ {
$data = $request->getParsedBody()['data']; $data = $request->getParsedBody()['data'];
$remote_element = \Courseware\StructuralElement::find($data['element']['id']); $sourceElement = StructuralElement::find($args['id']);
$parent_element = \Courseware\StructuralElement::find($data['parent_id']); $newParent = StructuralElement::find($data['parent_id']);
if (!Authority::canCreateContainer($user = $this->getUser($request), $parent_element)) { if (!Authority::canCreateContainer($user = $this->getUser($request), $newParent)) {
throw new AuthorizationFailedException(); throw new AuthorizationFailedException();
} }
$new_element = $this->copyElement($user, $remote_element, $parent_element); $newElement = $this->copyElement($user, $sourceElement, $newParent);
$response = $response->withHeader('Content-Type', 'application/json'); return $this->redirectToStructuralElement($response, $newElement);
$response->getBody()->write((string) json_encode($new_element));
return $response;
} }
private function copyElement(\User $user, \Courseware\StructuralElement $remote_element, \Courseware\StructuralElement $parent_element) private function copyElement(\User $user, StructuralElement $sourceElement, StructuralElement $newParent)
{ {
$new_element = $remote_element->copy($user, $parent_element); $new_element = $sourceElement->copy($user, $newParent);
return $new_element; return $new_element;
} }
}
\ No newline at end of file /**
* @SuppressWarnings(PHPMD.Superglobals)
*/
private function redirectToStructuralElement(Response $response, StructuralElement $resource): Response
{
$pathinfo = $this->getSchema($resource)
->getSelfLink($resource)
->getStringRepresentation($this->container->get('json-api-integration-urlPrefix'));
$old = \URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']);
$url = \URLHelper::getURL($pathinfo, [], true);
\URLHelper::setBaseURL($old);
return $response->withHeader('Location', $url)->withStatus(303);
}
}
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
</courseware-tab> </courseware-tab>
<courseware-tab :name="$gettext('Kopieren')"> <courseware-tab :name="$gettext('Kopieren')">
<courseware-manager-copy-selector @loadSelf="reloadElements"/> <courseware-manager-copy-selector @loadSelf="reloadElements" @reloadElement="reloadElements" />
</courseware-tab> </courseware-tab>
<courseware-tab :name="$gettext('Importieren')"> <courseware-tab :name="$gettext('Importieren')">
...@@ -233,6 +233,7 @@ export default { ...@@ -233,6 +233,7 @@ export default {
async reloadElements() { async reloadElements() {
await this.setCurrentId(this.currentId); await this.setCurrentId(this.currentId);
await this.setSelfId(this.selfId); await this.setSelfId(this.selfId);
this.$emit("reload");
}, },
async setCurrentId(target) { async setCurrentId(target) {
this.currentId = target; this.currentId = target;
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
:currentElement="remoteElement" :currentElement="remoteElement"
@selectElement="setRemoteId" @selectElement="setRemoteId"
@loadSelf="loadSelf" @loadSelf="loadSelf"
@reloadElement="reloadElement"
/> />
<courseware-companion-box <courseware-companion-box
v-if="remoteId === '' && hasRemoteCid" v-if="remoteId === '' && hasRemoteCid"
...@@ -106,9 +107,9 @@ export default { ...@@ -106,9 +107,9 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
loadAnotherCourseware: 'courseware-structure/loadAnotherCourseware',
loadUsersCourses: 'loadUsersCourses', loadUsersCourses: 'loadUsersCourses',
loadStructuralElement: 'loadStructuralElement', loadStructuralElement: 'loadStructuralElement',
loadRemoteCoursewareStructure: 'loadRemoteCoursewareStructure',
loadSemester: 'semesters/loadById', loadSemester: 'semesters/loadById',
}), }),
selectSource(source) { selectSource(source) {
...@@ -116,16 +117,15 @@ export default { ...@@ -116,16 +117,15 @@ export default {
}, },
async loadRemoteCourseware(cid) { async loadRemoteCourseware(cid) {
this.remoteCid = cid; this.remoteCid = cid;
this.remoteCoursewareInstance = await this.loadRemoteCoursewareStructure({rangeId: this.remoteCid, rangeType: 'courses'}); this.remoteCoursewareInstance = await this.loadAnotherCourseware({ id: this.remoteCid, type: 'courses'});
if (this.remoteCoursewareInstance !== null) { if (this.remoteCoursewareInstance !== null) {
this.setRemoteId(this.remoteCoursewareInstance.relationships.root.data.id); this.setRemoteId(this.remoteCoursewareInstance.relationships.root.data.id);
} else { } else {
this.remoteId = ''; this.remoteId = '';
} }
}, },
async loadOwnCourseware() { async loadOwnCourseware() {
this.ownCoursewareInstance = await this.loadRemoteCoursewareStructure({rangeId: this.userId, rangeType: 'users'}); this.ownCoursewareInstance = await this.loadAnotherCourseware({ id: this.userId, type: 'users' });
if (this.ownCoursewareInstance !== null) { if (this.ownCoursewareInstance !== null) {
this.setOwnId(this.ownCoursewareInstance.relationships.root.data.id); this.setOwnId(this.ownCoursewareInstance.relationships.root.data.id);
} else { } else {
...@@ -188,6 +188,9 @@ export default { ...@@ -188,6 +188,9 @@ export default {
} }
return 'seminar'; return 'seminar';
},
reloadElement() {
this.$emit("reloadElement");
} }
}, },
async mounted() { async mounted() {
......
...@@ -309,7 +309,7 @@ export default { ...@@ -309,7 +309,7 @@ export default {
let element = data.element; let element = data.element;
if (source === 'self') { if (source === 'self') {
element.relationships.parent.data.id = this.filingData.parentItem.id; element.relationships.parent.data.id = this.filingData.parentItem.id;
element.attributes.position = this.childrenById(this.filingData.parentItem.id).length; element.attributes.position = this.childrenById(this.filingData.parentItem.id).length + 1;
await this.lockObject({ id: element.id, type: 'courseware-structural-elements' }); await this.lockObject({ id: element.id, type: 'courseware-structural-elements' });
await this.updateStructuralElement({ await this.updateStructuralElement({
element: element, element: element,
...@@ -343,7 +343,7 @@ export default { ...@@ -343,7 +343,7 @@ export default {
let container = data.container; let container = data.container;
if (source === 'self') { if (source === 'self') {
container.relationships['structural-element'].data.id = this.filingData.parentItem.id; container.relationships['structural-element'].data.id = this.filingData.parentItem.id;
container.attributes.position = this.filingData.parentItem.relationships.containers.data.length; container.attributes.position = this.filingData.parentItem.relationships.containers.data.length + 1;
await this.lockObject({id: container.id, type: 'courseware-containers'}); await this.lockObject({id: container.id, type: 'courseware-containers'});
await this.updateContainer({ await this.updateContainer({
container: container, container: container,
...@@ -399,7 +399,7 @@ export default { ...@@ -399,7 +399,7 @@ export default {
await this.unlockObject({id: destinationContainer.id, type: 'courseware-containers'}); await this.unlockObject({id: destinationContainer.id, type: 'courseware-containers'});
block.relationships.container.data.id = this.filingData.parentItem.id; block.relationships.container.data.id = this.filingData.parentItem.id;
block.attributes.position = this.filingData.parentItem.relationships.blocks.data.length; block.attributes.position = this.filingData.parentItem.relationships.blocks.data.length + 1;
await this.lockObject({id: block.id, type: 'courseware-blocks'}); await this.lockObject({id: block.id, type: 'courseware-blocks'});
await this.updateBlock({ await this.updateBlock({
block: block, block: block,
......
<template> <template>
<courseware-course-manager></courseware-course-manager> <courseware-course-manager @reload="rebuildStructure"></courseware-course-manager>
</template> </template>
<script> <script>
...@@ -20,17 +20,21 @@ export default { ...@@ -20,17 +20,21 @@ export default {
invalidateStructureCache: 'courseware-structure/invalidateCache', invalidateStructureCache: 'courseware-structure/invalidateCache',
loadCoursewareStructure: 'courseware-structure/load', loadCoursewareStructure: 'courseware-structure/load',
}), }),
async rebuildStructure() {
// compute order of structural elements once more
await this.buildStructure();
console.debug("built structure")
// throw away stale cache
this.invalidateStructureCache();
},
}, },
async mounted() { async mounted() {
await this.loadCoursewareStructure(); await this.loadCoursewareStructure();
}, },
watch: { watch: {
async structuralElements(newElements, oldElements) { async structuralElements(newElements, oldElements) {
// compute order of structural elements once more this.rebuildStructure();
await this.buildStructure();
// throw away stale cache
this.invalidateStructureCache();
}, },
}, },
}; };
......
...@@ -317,17 +317,14 @@ export const actions = { ...@@ -317,17 +317,14 @@ export const actions = {
// console.log(resp); // console.log(resp);
}); });
}, },
copyStructuralElement({ getters }, { parentId, element }) { copyStructuralElement({ dispatch, getters }, { parentId, element }) {
const copy = { const copy = { data: { parent_id: parentId, }, };
data: {
element: element, return state.httpClient.post(`courseware-structural-elements/${element.id}/copy`, copy)
parent_id: parentId, .then(({ data }) => {
}, const id = data.data.id;
}; dispatch('loadStructuralElement', id);
});
return state.httpClient.post(`courseware-structural-elements/${element.id}/copy`, copy).then((resp) => {
// console.log(resp);
});
}, },
lockObject({ dispatch, getters }, { id, type }) { lockObject({ dispatch, getters }, { id, type }) {
......
...@@ -31,14 +31,21 @@ export const mutations = { ...@@ -31,14 +31,21 @@ export const mutations = {
const actions = { const actions = {
build({ commit, rootGetters }) { build({ commit, rootGetters }) {
const structuralElements = rootGetters['courseware-structural-elements/all']; const instance = rootGetters['courseware'];
const root = findRoot(structuralElements); if (!instance) {
throw new Error('Could not find current courseware');
}
const root = rootGetters['courseware-structural-elements/related']({
parent: { id: instance.id, type: instance.type },
relationship: 'root',
});
if (!root) { if (!root) {
commit('reset'); commit('reset');
return; return;
} }
const structuralElements = rootGetters['courseware-structural-elements/all'];
const children = structuralElements.reduce((memo, element) => { const children = structuralElements.reduce((memo, element) => {
const parent = element.relationships.parent?.data?.id ?? null; const parent = element.relationships.parent?.data?.id ?? null;
if (parent) { if (parent) {
...@@ -81,43 +88,70 @@ const actions = { ...@@ -81,43 +88,70 @@ const actions = {
} }
}, },
// load the structure of the current courseware
async load({ commit, dispatch, rootGetters }) { async load({ commit, dispatch, rootGetters }) {
const parent = rootGetters['context']; const context = rootGetters['context'];
const relationship = 'courseware'; const instance = await dispatch('loadInstance', context);
const options = { commit('coursewareSet', instance, { root: true });
include: 'bookmarks,root',
};
// get courseware instance const root = rootGetters['courseware-structural-elements/related']({
await dispatch(`courseware-instances/loadRelated`, { parent, relationship, options }, { root: true }); parent: { id: instance.id, type: instance.type },
const courseware = rootGetters['courseware-instances/all'][0]; relationship: 'root',
commit('coursewareSet', courseware, { root: true }); });
if (!root) {
throw new Error(`Could not find root of courseware { id: ${instance.id}, type: ${instance.type}`);
}
dispatch('fetchDescendantsWithCaching', { root });
// load descendants return instance;
dispatch('fetchDescendants');
}, },
async fetchDescendants({ dispatch, rootGetters, commit }) { // load the structure of a specified courseware
// get root of that instance async loadAnotherCourseware({ commit, dispatch, rootGetters }, context) {
const courseware = rootGetters['courseware']; const instance = await dispatch('loadInstance', context);
if (!courseware) {
return; const root = rootGetters['courseware-structural-elements/related']({
} parent: { id: instance.id, type: instance.type },
const rootElement = rootGetters['courseware-structural-elements/related']({
parent: { id: courseware.id, type: courseware.type },
relationship: 'root', relationship: 'root',
}); });
if (!rootElement) { if (!root) {
return; throw new Error(`Could not find root of courseware { id: ${instance.id}, type: ${instance.type}`);
} }
await dispatch('loadDescendants', { root });
return instance;
},
loadInstance({ commit, dispatch, rootGetters }, context) {
const parent = context;
const relationship = 'courseware';
const options = {
include: 'bookmarks,root',
};
return dispatch(
`courseware-instances/loadRelated`,
{
parent,
relationship,
options,
},
{ root: true }
).then(() => {
return rootGetters['courseware-instances/related']({ parent, relationship });
});
},
async fetchDescendantsWithCaching({ dispatch, rootGetters, commit }, { root }) {
const cache = window.STUDIP.Cache.getInstance('courseware'); const cache = window.STUDIP.Cache.getInstance('courseware');
const cacheKey = `descendants/${rootElement.id}/${rootGetters['userId']}`; const cacheKey = `descendants/${root.id}/${rootGetters['userId']}`;
await unpickleDescendants(); await unpickleStaleDescendants();
revalidateDescendants(); return revalidateDescendants();
function unpickleDescendants() { function unpickleStaleDescendants() {
try { try {
const descendants = cache.get(cacheKey); const descendants = cache.get(cacheKey);
const cacheHit = descendants !== undefined; const cacheHit = descendants !== undefined;
...@@ -130,22 +164,7 @@ const actions = { ...@@ -130,22 +164,7 @@ const actions = {
} }
function revalidateDescendants() { function revalidateDescendants() {
return loadDescendants().then(removeStaleElements).then(pickleDescendants); return dispatch('loadDescendants', { root }).then(removeStaleElements).then(pickleDescendants);
}
function loadDescendants() {
const parent = { id: rootElement.id, type: rootElement.type };
const relationship = 'descendants';
const options = {
'page[offset]': 0,
'page[limit]': 10000,
};
return dispatch(
'courseware-structural-elements/loadRelated',
{ parent, relationship, options },
{ root: true }
);
} }
function pickleDescendants() { function pickleDescendants() {
...@@ -156,9 +175,9 @@ const actions = { ...@@ -156,9 +175,9 @@ const actions = {
function removeStaleElements() { function removeStaleElements() {
const idsToKeep = [ const idsToKeep = [
rootElement.id, root.id,
...rootGetters['courseware-structural-elements/related']({ ...rootGetters['courseware-structural-elements/related']({
parent: rootElement, parent: root,
relationship: 'descendants', relationship: 'descendants',
}).map(({ id }) => id), }).map(({ id }) => id),
]; ];
...@@ -168,11 +187,22 @@ const actions = { ...@@ -168,11 +187,22 @@ const actions = {
.forEach((id) => commit('courseware-structural-elements/REMOVE_RECORD', { id }, { root: true })); .forEach((id) => commit('courseware-structural-elements/REMOVE_RECORD', { id }, { root: true }));
} }
}, },
};
function findRoot(nodes) { loadDescendants({ dispatch }, { root }) {
return nodes.find((node) => !node.relationships.parent?.data); const parent = { id: root.id, type: root.type };
} const relationship = 'descendants';
const options = {
'page[offset]': 0,
'page[limit]': 10000,
};
return dispatch(
'courseware-structural-elements/loadRelated',
{ parent, relationship, options },
{ root: true }
);
},
};
function* visitTree(tree, current) { function* visitTree(tree, current) {
if (current) { if (current) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment