Skip to content

Commit

Permalink
Merge pull request #13008 from AlexVelezLl/integrate-search-resource-…
Browse files Browse the repository at this point in the history
…selection

Integrate search resource selection
  • Loading branch information
marcellamaki authored Feb 4, 2025
2 parents 0a64cbc + 696cfa9 commit 4b993e4
Show file tree
Hide file tree
Showing 22 changed files with 799 additions and 105 deletions.
2 changes: 2 additions & 0 deletions kolibri/plugins/coach/assets/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class CoachToolsModule extends KolibriApp {
PageNames.LESSON_PREVIEW_SELECTED_RESOURCES,
PageNames.LESSON_PREVIEW_RESOURCE,
PageNames.LESSON_SELECT_RESOURCES_INDEX,
PageNames.LESSON_SELECT_RESOURCES_SEARCH,
PageNames.LESSON_SELECT_RESOURCES_SEARCH_RESULTS,
PageNames.LESSON_SELECT_RESOURCES_BOOKMARKS,
PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import uniqBy from 'lodash/uniqBy';
import { ref, computed, getCurrentInstance, watch } from 'vue';
import ContentNodeResource from 'kolibri-common/apiResources/ContentNodeResource';
import ChannelResource from 'kolibri-common/apiResources/ChannelResource';
import useBaseSearch from 'kolibri-common/composables/useBaseSearch';
import useFetch from './useFetch';

/**
Expand All @@ -13,6 +14,11 @@ import useFetch from './useFetch';
* This utility handles selection rules, manages fetch states for channels, bookmarks,
* and topic trees, and offers methods to add, remove, or override selected resources.
*
* @param {Object} options
* @param {string} options.searchResultsRouteName The name of the route where the search results
* will be displayed so that we can redirect to it when the search terms are updated.
*
*
* @typedef {Object} UseResourceSelectionResponse
* @property {Object} topic Topic tree object, contains the information of the topic,
* its ascendants and children.
Expand All @@ -26,6 +32,10 @@ import useFetch from './useFetch';
* fetching bookmarks. Fetching more bookmarks is supported.
* @property {FetchObject} treeFetch Topic tree fetch object to manage the process of
* fetching topic trees and their resources. Fetching more resources is supported.
* @property {FetchObject} searchFetch Search fetch object to manage the process of
* fetching search results. Fetching more search results is supported.
* @property {Array<string>} searchTerms The search terms used to filter the search results.
* @property {boolean} displayingSearchResults Indicates whether we currently have search terms.
* @property {Array<(node: Object) => boolean>} selectionRules An array of functions that determine
* whether a node can be selected.
* @property {Array<Object>} selectedResources An array of currently selected resources.
Expand All @@ -35,10 +45,13 @@ import useFetch from './useFetch';
* from the `selectedResources` array.
* @property {(resources: Array<Object>) => void} setSelectedResources Replaces the current
* `selectedResources` array with the provided resources array.
* @property {() => void} clearSearch Clears the current search terms and results.
* @property {(tag: Object) => void} removeSearchFilterTag Removes the specified tag from the
* search terms.
*
* @returns {UseResourceSelectionResponse}
*/
export default function useResourceSelection() {
export default function useResourceSelection({ searchResultsRouteName } = {}) {
const store = getCurrentInstance().proxy.$store;
const route = computed(() => store.state.route);
const topicId = computed(() => route.value.query.topicId);
Expand Down Expand Up @@ -67,8 +80,49 @@ export default function useResourceSelection() {
}),
});

const waitForTopicLoad = () => {
const { searchTopicId } = route.value.query;
const topicToWaitFor = searchTopicId || topicId.value;
if (!topicToWaitFor || topicToWaitFor === topic.value?.id) {
return Promise.resolve();
}
return new Promise(resolve => {
const unwatch = watch(topic, () => {
if (topic.value?.id === topicToWaitFor) {
unwatch();
resolve();
}
});
});
};

const useSearchObject = useBaseSearch({
descendant: topic,
searchResultsRouteName,
// As we dont always show the search filters, we dont need to reload the search results
// each time the topic changes if not needed
reloadOnDescendantChange: false,
});
const searchFetch = {
data: useSearchObject.results,
loading: useSearchObject.searchLoading,
hasMore: computed(() => !!useSearchObject.more.value),
loadingMore: useSearchObject.moreLoading,
fetchData: async () => {
// Make sure that the topic is loaded before searching
await waitForTopicLoad();
return useSearchObject.search();
},
fetchMore: useSearchObject.searchMore,
};

const { displayingSearchResults } = useSearchObject;

const fetchTree = async (params = {}) => {
topic.value = await ContentNodeResource.fetchTree(params);
const newTopic = await ContentNodeResource.fetchTree(params);
if (topic.value?.id !== newTopic.id) {
topic.value = newTopic;
}
return topic.value.children;
};

Expand All @@ -80,11 +134,13 @@ export default function useResourceSelection() {
watch(topicId, () => {
if (topicId.value) {
treeFetch.fetchData();
} else {
topic.value = null;
}
});

const loading = computed(() => {
const sources = [bookmarksFetch, channelsFetch, treeFetch];
const sources = [bookmarksFetch, channelsFetch, treeFetch, searchFetch];

return sources.some(sourceFetch => sourceFetch.loading.value);
});
Expand All @@ -95,6 +151,9 @@ export default function useResourceSelection() {
if (topicId.value) {
treeFetch.fetchData();
}
if (displayingSearchResults.value) {
searchFetch.fetchData();
}
};

fetchInitialData();
Expand Down Expand Up @@ -129,13 +188,18 @@ export default function useResourceSelection() {
return {
topic,
loading,
treeFetch,
channelsFetch,
bookmarksFetch,
treeFetch,
searchFetch,
selectionRules,
selectedResources,
searchTerms: useSearchObject.searchTerms,
displayingSearchResults: useSearchObject.displayingSearchResults,
selectResources,
deselectResources,
setSelectedResources,
clearSearch: useSearchObject.clearSearch,
removeSearchFilterTag: useSearchObject.removeFilterTag,
};
}
2 changes: 2 additions & 0 deletions kolibri/plugins/coach/assets/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ export const PageNames = {
LESSON_EDIT_DETAILS_BETTER: 'LESSON_EDIT_DETAILS_BETTER',
LESSON_SELECT_RESOURCES: 'LESSON_SELECT_RESOURCES',
LESSON_SELECT_RESOURCES_INDEX: 'LESSON_SELECT_RESOURCES_INDEX',
LESSON_SELECT_RESOURCES_SEARCH: 'LESSON_SELECT_RESOURCES_SEARCH',
LESSON_SELECT_RESOURCES_BOOKMARKS: 'LESSON_SELECT_RESOURCES_BOOKMARKS',
LESSON_SELECT_RESOURCES_TOPIC_TREE: 'LESSON_SELECT_RESOURCES_TOPIC_TREE',
LESSON_SELECT_RESOURCES_SEARCH_RESULTS: 'LESSON_SELECT_RESOURCES_SEARCH_RESULTS',
LESSON_PREVIEW_SELECTED_RESOURCES: 'LESSON_PREVIEW_SELECTED_RESOURCES',
LESSON_PREVIEW_RESOURCE: 'LESSON_PREVIEW_RESOURCE',
LESSON_LEARNER_REPORT: 'LESSON_LEARNER_REPORT',
Expand Down
21 changes: 16 additions & 5 deletions kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ import LessonLearnerExercisePage from '../views/lessons/reports/LessonLearnerExe
import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.vue';
import EditLessonDetails from '../views/lessons/LessonSummaryPage/sidePanels/EditLessonDetails';
import PreviewSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/PreviewSelectedResources';
import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection';
import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue';
import SearchFilters from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SearchFilters.vue';
import SelectionIndex from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue';
import SelectFromBookmarks from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue';
import SelectFromChannels from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue';
import SelectFromTopicTree from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromTopicTree.vue';
import SelectFromSearchResults from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromSearchResults.vue';
import ManageSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue';

import { classIdParamRequiredGuard, RouteSegments } from './utils';

const {
Expand Down Expand Up @@ -153,8 +154,18 @@ export default [
},
{
name: PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE,
path: 'channels',
component: SelectFromChannels,
path: 'topic-tree',
component: SelectFromTopicTree,
},
{
name: PageNames.LESSON_SELECT_RESOURCES_SEARCH,
path: 'search',
component: SearchFilters,
},
{
name: PageNames.LESSON_SELECT_RESOURCES_SEARCH_RESULTS,
path: 'search-results',
component: SelectFromSearchResults,
},
{
name: PageNames.LESSON_PREVIEW_SELECTED_RESOURCES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
:content="content"
:message="contentCardMessage(content)"
:link="contentCardLink(content)"
:headingLevel="cardsHeadingLevel"
>
<template #notice>
<slot
Expand Down Expand Up @@ -161,6 +162,11 @@
type: Function, // ContentNode => Route
required: true,
},
// Heading level for the cards
cardsHeadingLevel: {
type: Number,
default: 3,
},
},
computed: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
:class="{ 'title-message-wrapper': Boolean(!windowIsSmall) }"
:style="{ color: $themeTokens.text }"
>
<h3
<component
:is="headingElement"
class="title"
dir="auto"
>
<KTextTruncator
:text="content.title"
:maxLines="2"
/>
</h3>
</component>
</div>
<KTextTruncator
v-if="!windowIsSmall"
Expand Down Expand Up @@ -101,11 +102,27 @@
type: String,
default: '',
},
headingLevel: {
type: Number,
default: 3,
validator(value) {
if (value <= 6 && value >= 2) {
return true;
} else {
// eslint-disable-next-line no-console
console.error(`'headingLevel' must be between 2 and 6.`);
return false;
}
},
},
},
computed: {
isTopic() {
return !this.content.isLeaf;
},
headingElement() {
return `h${this.headingLevel}`;
},
},
};
Expand Down Expand Up @@ -151,6 +168,7 @@
.title {
margin-bottom: 0.5em;
font-size: 1em;
}
.message {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@
},
computed: {
selectionCrumbs() {
return [
// The "Channels" breadcrumb
{ text: this.coreString('channelsLabel'), link: this.channelsLink },
// Ancestors breadcrumbs
// NOTE: The current topic is injected into `ancestors` in the showPage action
...this.ancestors.map(a => ({
text: a.title,
link: this.topicsLink(a.id),
})),
];
// NOTE: The current topic is injected into `ancestors` in the parent component
const breadcrumbs = this.ancestors.map(a => ({
text: a.title,
link: this.topicsLink(a.id),
}));
if (this.channelsLink) {
breadcrumbs.unshift({
text: this.coreString('channelsLabel'),
link: this.channelsLink,
});
}
return breadcrumbs;
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="select-resource">
<div>
<ResourceSelectionBreadcrumbs
v-if="topic"
v-if="topic && !hideBreadcrumbs"
:ancestors="[...topic.ancestors, topic]"
:channelsLink="channelsLink"
:topicsLink="topicsLink"
Expand All @@ -20,6 +20,7 @@
:contentCheckboxDisabled="contentCheckboxDisabled"
:contentCardLink="contentLink"
:showRadioButtons="!multi"
:cardsHeadingLevel="cardsHeadingLevel"
@changeselectall="handleSelectAll"
@change_content_card="toggleSelected"
@moreresults="fetchMore"
Expand All @@ -36,7 +37,7 @@
import { ContentNodeKinds } from 'kolibri/constants';
import ContentCardList from '../../lessons/LessonResourceSelectionPage/ContentCardList.vue';
import ResourceSelectionBreadcrumbs from '../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue';
import { PageNames, ViewMoreButtonStates } from '../../../constants';
import { ViewMoreButtonStates } from '../../../constants';
export default {
name: 'UpdatedResourceSelection',
Expand Down Expand Up @@ -94,13 +95,26 @@
type: Boolean,
default: false,
},
cardsHeadingLevel: {
type: Number,
default: 3,
},
channelsLink: {
type: Object,
required: false,
default: null,
},
getTopicLink: {
type: Function,
required: false,
default: () => {},
},
hideBreadcrumbs: {
type: Boolean,
default: false,
},
},
computed: {
channelsLink() {
return {
name: PageNames.LESSON_SELECT_RESOURCES_INDEX,
};
},
selectAllIndeterminate() {
return (
!this.selectAllChecked &&
Expand Down Expand Up @@ -142,6 +156,11 @@
return { name, params, query };
},
topicsLink(topicId) {
const route = this.getTopicLink?.(topicId);
if (route) {
return route;
}
const { name, params, query } = this.$route;
return {
name,
Expand Down
Loading

0 comments on commit 4b993e4

Please sign in to comment.