diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.spec.js index 89c7f526fb..9c7a8f457d 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.spec.js @@ -36,6 +36,11 @@ function mountComponent(opts = {}) { namespaced: true, getters: { isNodeInCopyingState: () => jest.fn(), + getContentNodesCount: () => + jest.fn().mockReturnValue({ + resource_count: TOPIC_NODE.resource_count, + assessment_item_count: EXERCISE_NODE.assessment_item_count, + }), }, }, }, diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue index f7fc43c8a9..55e0941d27 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue @@ -258,7 +258,11 @@ }; }, computed: { - ...mapGetters('contentNode', ['isNodeInCopyingState', 'hasNodeCopyingErrored']), + ...mapGetters('contentNode', [ + 'isNodeInCopyingState', + 'hasNodeCopyingErrored', + 'getContentNodesCount', + ]), isCompact() { return this.compact || !this.$vuetify.breakpoint.mdAndUp; }, @@ -273,14 +277,15 @@ return { title, kind, src, encoding }; }, subtitle() { + const count = this.getContentNodesCount(this.node.id); switch (this.node.kind) { case ContentKindsNames.TOPIC: return this.$tr('resources', { - value: this.node.resource_count || 0, + value: count?.resource_count || 0, }); case ContentKindsNames.EXERCISE: return this.$tr('questions', { - value: this.node.assessment_item_count || 0, + value: count?.assessment_item_count || 0, }); } diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue index ad498da058..6c5d676a5b 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue @@ -447,7 +447,10 @@ this.hideHTMLScroll(false); this.$router.push({ name: RouteNames.TREE_VIEW, - params: { nodeId: this.$route.params.nodeId }, + params: { + nodeId: this.$route.params.nodeId, + addedCount: this.nodeIds.length, + }, }); }, hideHTMLScroll(hidden) { diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js index 054fba2b6b..013cf754f4 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js @@ -33,6 +33,7 @@ const GETTERS = { getContentNodeAncestors: () => jest.fn(), getContentNode: () => jest.fn(), isNodeInCopyingState: () => jest.fn(), + getContentNodesCount: () => jest.fn(), }, }; diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/NodePanel.vue b/contentcuration/contentcuration/frontend/channelEdit/views/NodePanel.vue index e8d9dcaa2d..d7dd308d73 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/NodePanel.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/NodePanel.vue @@ -46,7 +46,7 @@
- + {{ $tr('showMore') }}
@@ -105,16 +105,37 @@ isRoot() { return this.rootId === this.parentId; }, + addedCount() { + return this.$route.params.addedCount; + }, + displayShowMoreButton() { + // Handle inconsistency with this.more that causes double click on "Show more" to load + // more nodes when new nodes(exercises, folders or file uploads) are added to the channel. + // If the addedCount is equal to the children length, force hide the "Show more" button. + const moreAdditions = this.addedCount !== this.children.length ? this.more : null; + return this.addedCount ? moreAdditions : this.more; + }, }, created() { this.loading = true; - this.loadChildren({ parent: this.parentId }).then(childrenResponse => { - this.loading = false; - this.more = childrenResponse.more || null; + this.clearContentNodes().then(success => { + if (success) { + this.loadChildren({ parent: this.parentId }).then(childrenResponse => { + this.loading = false; + this.more = childrenResponse.more || null; + const children = childrenResponse?.results || []; + this.setContentNodesCount(children); + }); + } }); }, methods: { - ...mapActions('contentNode', ['loadChildren', 'loadContentNodes']), + ...mapActions('contentNode', [ + 'loadChildren', + 'loadContentNodes', + 'setContentNodesCount', + 'clearContentNodes', + ]), goToNodeDetail(nodeId) { if ( this.$route.params.nodeId === this.parentId && @@ -164,6 +185,8 @@ this.loadContentNodes(this.more).then(response => { this.more = response.more || null; this.moreLoading = false; + const children = response?.results || []; + this.setContentNodesCount(children); }); } }, @@ -193,8 +216,8 @@ .pagination-container { display: flex; - justify-content: flex-start; - margin: 4px; + justify-content: space-evenly; + margin: 32px; } diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js index 70dc763762..8057041832 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js @@ -610,3 +610,17 @@ export async function checkSavingProgress( export function setQuickEditModal(context, open) { context.commit('SET_QUICK_EDIT_MODAL', open); } + +export function setContentNodesCount(context, nodes) { + for (const node of nodes) { + const { id, assessment_item_count, resource_count } = node; + context.commit('SET_CONTENTNODES_COUNT', { id, resource_count, assessment_item_count }); + } +} + +export function clearContentNodes(context) { + return new Promise(resolve => { + context.commit('CLEAR_CONTENTNODES'); + resolve(true); + }); +} diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js index 4cbcadace2..177471557a 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js @@ -365,3 +365,9 @@ export function getQuickEditModalOpen(state) { return state.quickEditModalOpen; }; } + +export function getContentNodesCount(state) { + return function(nodeId) { + return state.contentNodesCountMap[nodeId]; + }; +} diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js index 0d900fabe3..49a4260063 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js @@ -75,6 +75,17 @@ export default { * } */ previousStepsMap: {}, + + /** + * A map of node ids to their respective resource or assessment item nodes counts. + * + * For example, + * { + * '': { 'resourceCount': 2 } + * '': { 'assessmentItemCount': 1 } + * } + */ + contentNodesCountMap: {}, }; }, getters, diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js index cdbadfc23c..da3e78f478 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js @@ -138,3 +138,23 @@ export function SAVE_NEXT_STEPS(state, { mappings = [] } = {}) { ADD_PREVIOUS_STEP(state, mapping); } } + +/** + * Saves the content node count to vuex state. + * @param state - The vuex state + * @param id - The content node id + * @param assessment_item_count - The count of assessment items + * @param resource_count - The count of resources + */ +export function SET_CONTENTNODES_COUNT(state, { id, assessment_item_count, resource_count }) { + Vue.set(state.contentNodesCountMap, id, { resource_count, assessment_item_count }); +} + +/** + * Clears all content node data (nodes and associated counts) from the vuex state. + * @param state - The vuex state + */ +export function CLEAR_CONTENTNODES(state) { + state.contentNodesMap = {}; + state.contentNodesCountMap = {}; +}