|
49 | 49 | v-else-if="!displayingSearchResults && !rootNodesLoading" |
50 | 50 | data-test="channels" |
51 | 51 | > |
52 | | - <h1 class="channels-label"> |
53 | | - {{ channelsLabel }} |
54 | | - </h1> |
| 52 | + <KFixedGrid numCols="4"> |
| 53 | + <KFixedGridItem span="3"> |
| 54 | + <h1 class="channels-label"> |
| 55 | + {{ channelsLabel }} |
| 56 | + </h1> |
| 57 | + </KFixedGridItem> |
| 58 | + <KFixedGridItem span="1"> |
| 59 | + <LanguageSelector :primary="true" /> |
| 60 | + </KFixedGridItem> |
| 61 | + </KFixedGrid> |
55 | 62 | <p |
56 | 63 | v-if="isLocalLibraryEmpty" |
57 | 64 | data-test="nothing-in-lib-label" |
|
88 | 95 | data-test="search-results" |
89 | 96 | :allowDownloads="allowDownloads" |
90 | 97 | :results="results" |
91 | | - :removeFilterTag="removeFilterTag" |
92 | 98 | :clearSearch="clearSearch" |
93 | 99 | :moreLoading="moreLoading" |
94 | 100 | :searchMore="searchMore" |
|
109 | 115 | :class="windowIsLarge ? 'side-panel' : ''" |
110 | 116 | data-test="side-panel-local" |
111 | 117 | :width="`${sidePanelWidth}px`" |
| 118 | + :showLanguages="displayingSearchResults" |
112 | 119 | /> |
113 | 120 | </div> |
114 | 121 |
|
|
122 | 129 | v-model="searchTerms" |
123 | 130 | data-test="side-panel" |
124 | 131 | :width="`${sidePanelWidth}px`" |
| 132 | + :showLanguages="displayingSearchResults" |
125 | 133 | /> |
126 | 134 | </SidePanelModal> |
127 | 135 |
|
|
187 | 195 | import MeteredConnectionNotificationModal from 'kolibri-common/components/MeteredConnectionNotificationModal.vue'; |
188 | 196 | import appCapabilities, { checkCapability } from 'kolibri/utils/appCapabilities'; |
189 | 197 | import LearningActivityChip from 'kolibri-common/components/ResourceDisplayAndSearch/LearningActivityChip.vue'; |
190 | | - import { searchKeys } from 'kolibri-common/composables/useBaseSearch'; |
191 | 198 | import SidePanelModal from 'kolibri-common/components/SidePanelModal'; |
192 | 199 | import SearchFiltersPanel from 'kolibri-common/components/SearchFiltersPanel'; |
| 200 | + import LanguageSelector from 'kolibri-common/components/SearchFiltersPanel/LanguageSelector'; |
| 201 | + import { allLanguagesValue } from 'kolibri-common/composables/useBaseSearch'; |
193 | 202 | import useChannels from 'kolibri-common/composables/useChannels'; |
194 | 203 | import { KolibriStudioId, PageNames } from '../../constants'; |
195 | 204 | import useCardViewStyle from '../../composables/useCardViewStyle'; |
|
203 | 212 | } from '../../composables/useDevices'; |
204 | 213 | import useSearch from '../../composables/useSearch'; |
205 | 214 | import useLearnerResources from '../../composables/useLearnerResources'; |
| 215 | + import { sortChannels } from '../../utils/sortChannels'; |
206 | 216 | import BrowseResourceMetadata from '../BrowseResourceMetadata'; |
207 | 217 | import commonLearnStrings from '../commonLearnStrings'; |
208 | 218 | import ChannelCardGroupGrid from '../ChannelCardGroupGrid'; |
|
233 | 243 | LearnAppBarPage, |
234 | 244 | OtherLibraries, |
235 | 245 | PostSetupModalGroup, |
| 246 | + LanguageSelector, |
236 | 247 | }, |
237 | 248 | mixins: [commonLearnStrings, commonCoreStrings], |
238 | 249 | setup(props) { |
239 | 250 | const currentInstance = getCurrentInstance().proxy; |
240 | 251 | const store = currentInstance.$store; |
241 | 252 | const router = currentInstance.$router; |
242 | 253 |
|
243 | | - const { |
244 | | - isUserLoggedIn, |
245 | | - isCoach, |
246 | | - isAdmin, |
247 | | - isSuperuser, |
248 | | - canManageContent, |
249 | | - isLearnerOnlyImport, |
250 | | - } = useUser(); |
| 254 | + const { isUserLoggedIn, canManageContent, isLearnerOnlyImport } = useUser(); |
251 | 255 | const { allowDownloadOnMeteredConnection } = useDeviceSettings(); |
252 | 256 | const { |
253 | 257 | searchTerms, |
|
258 | 262 | more, |
259 | 263 | search, |
260 | 264 | searchMore, |
261 | | - removeFilterTag, |
262 | 265 | clearSearch, |
263 | 266 | currentRoute, |
| 267 | + createBaseSearchGetParams, |
| 268 | + primaryLanguage, |
264 | 269 | } = useSearch(); |
265 | 270 | search(); |
266 | 271 | const { fetchResumableContentNodes } = useLearnerResources(); |
|
288 | 293 | fetchResumableContentNodes(); |
289 | 294 | } |
290 | 295 | const shouldResolve = samePageCheckGenerator(store); |
| 296 | + const getParams = createBaseSearchGetParams(); |
| 297 | + getParams.parent__isnull = true; |
291 | 298 | return ContentNodeResource.fetchCollection({ |
292 | | - getParams: { |
293 | | - parent__isnull: true, |
294 | | - include_coach_content: get(isAdmin) || get(isCoach) || get(isSuperuser), |
295 | | - baseurl, |
296 | | - }, |
| 299 | + getParams, |
297 | 300 | }).then( |
298 | 301 | channelCollection => { |
299 | 302 | if (shouldResolve()) { |
300 | 303 | // we want them to be in the same order as the channels list |
301 | 304 | set( |
302 | 305 | rootNodes, |
303 | | - channels |
304 | | - .map(channel => { |
305 | | - const node = channelCollection.find(n => n.channel_id === channel.id); |
306 | | - if (node) { |
307 | | - // The `channel` comes with additional data that is |
308 | | - // not returned from the ContentNodeResource. |
309 | | - // Namely thumbnail, description and tagline (so far) |
310 | | - node.title = channel.name || node.title; |
311 | | - node.thumbnail = channel.thumbnail; |
312 | | - node.description = channel.tagline || channel.description; |
313 | | - return node; |
314 | | - } |
315 | | - }) |
316 | | - .filter(Boolean), |
| 306 | + // Sort channels by relevance to the current user interface language |
| 307 | + sortChannels( |
| 308 | + channels |
| 309 | + .map(channel => { |
| 310 | + const node = channelCollection.find(n => n.channel_id === channel.id); |
| 311 | + if (node) { |
| 312 | + // The `channel` comes with additional data that is |
| 313 | + // not returned from the ContentNodeResource. |
| 314 | + // Namely thumbnail, description and tagline (so far) |
| 315 | + node.title = channel.name || node.title; |
| 316 | + node.thumbnail = channel.thumbnail; |
| 317 | + node.description = channel.tagline || channel.description; |
| 318 | + node.included_languages = channel.included_languages; |
| 319 | + return node; |
| 320 | + } |
| 321 | + }) |
| 322 | + // Filter out any channels that are not in the user's primary language |
| 323 | + // we have filtered this in the initial API fetch, but for older versions |
| 324 | + // of Kolibri, the API filter will be a noop. |
| 325 | + .filter( |
| 326 | + c => |
| 327 | + (c && get(primaryLanguage) === allLanguagesValue) || |
| 328 | + c.included_languages.includes(get(primaryLanguage)), |
| 329 | + ), |
| 330 | + get(primaryLanguage) === allLanguagesValue ? null : get(primaryLanguage), |
| 331 | + ), |
317 | 332 | ); |
318 | 333 |
|
319 | 334 | store.commit('CORE_SET_PAGE_LOADING', false); |
|
332 | 347 | } |
333 | 348 |
|
334 | 349 | function _showLibrary(baseurl) { |
335 | | - return fetchChannels({ baseurl }).then(channels => { |
| 350 | + const params = createBaseSearchGetParams(); |
| 351 | + return fetchChannels(params).then(channels => { |
336 | 352 | if (!channels.length && isUserLoggedIn) { |
337 | 353 | router.replace({ name: PageNames.CONTENT_UNAVAILABLE }); |
338 | 354 | return; |
|
342 | 358 | return; |
343 | 359 | } |
344 | 360 |
|
345 | | - const query = currentRoute().query; |
346 | | -
|
347 | | - if (searchKeys.some(key => query[key])) { |
| 361 | + if (get(displayingSearchResults)) { |
348 | 362 | // If currently on a route with search terms |
349 | 363 | // just finish early and let the component handle loading |
350 | 364 | store.commit('CORE_SET_PAGE_LOADING', false); |
|
386 | 400 | } |
387 | 401 | }); |
388 | 402 |
|
| 403 | + watch(primaryLanguage, () => { |
| 404 | + if (!displayingSearchResults.value) { |
| 405 | + showLibrary(); |
| 406 | + } |
| 407 | + }); |
| 408 | +
|
389 | 409 | showLibrary(); |
390 | 410 |
|
391 | 411 | return { |
|
399 | 419 | results, |
400 | 420 | more, |
401 | 421 | searchMore, |
402 | | - removeFilterTag, |
403 | 422 | clearSearch, |
404 | 423 | windowBreakpoint, |
405 | 424 | windowIsLarge, |
|
0 commit comments