|
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 | + <KSelect |
| 60 | + v-model="currentChannelLanguage" |
| 61 | + data-test="channel-language-select" |
| 62 | + :label="'Show channels for language:'" |
| 63 | + :options="languageFilterOptions" |
| 64 | + clearable |
| 65 | + /> |
| 66 | + </KFixedGridItem> |
| 67 | + </KFixedGrid> |
55 | 68 | <p |
56 | 69 | v-if="isLocalLibraryEmpty" |
57 | 70 | data-test="nothing-in-lib-label" |
|
175 | 188 |
|
176 | 189 | <script> |
177 | 190 |
|
| 191 | + import isEmpty from 'lodash/isEmpty'; |
| 192 | + import sortBy from 'lodash/sortBy'; |
| 193 | + import uniqBy from 'lodash/uniqBy'; |
178 | 194 | import { get, set } from '@vueuse/core'; |
179 | 195 |
|
180 | | - import { onMounted, getCurrentInstance, ref, watch } from '@vue/composition-api'; |
| 196 | + import { computed, onMounted, getCurrentInstance, ref, watch } from '@vue/composition-api'; |
181 | 197 | import commonCoreStrings from 'kolibri/uiText/commonCoreStrings'; |
182 | 198 | import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow'; |
183 | 199 | import useUser from 'kolibri/composables/useUser'; |
| 200 | + import { getContentLangActive } from 'kolibri/utils/i18n'; |
184 | 201 | import samePageCheckGenerator from 'kolibri-common/utils/samePageCheckGenerator'; |
185 | 202 | import ContentNodeResource from 'kolibri-common/apiResources/ContentNodeResource'; |
186 | 203 | import { mapState } from 'vuex'; |
|
286 | 303 | } |
287 | 304 | }); |
288 | 305 |
|
289 | | - const rootNodes = ref([]); |
| 306 | + const _rootNodes = ref([]); |
290 | 307 | const rootNodesLoading = ref(false); |
291 | 308 |
|
| 309 | + const routeChannelLanguage = computed(() => currentRoute().query.channelLanguage); |
| 310 | +
|
292 | 311 | function _showChannels(channels, baseurl) { |
293 | 312 | if (get(isUserLoggedIn) && !baseurl) { |
294 | 313 | fetchResumableContentNodes(); |
|
305 | 324 | if (shouldResolve()) { |
306 | 325 | // we want them to be in the same order as the channels list |
307 | 326 | set( |
308 | | - rootNodes, |
| 327 | + _rootNodes, |
309 | 328 | channels |
310 | 329 | .map(channel => { |
311 | 330 | const node = channelCollection.find(n => n.channel_id === channel.id); |
|
316 | 335 | node.title = channel.name || node.title; |
317 | 336 | node.thumbnail = channel.thumbnail; |
318 | 337 | node.description = channel.tagline || channel.description; |
| 338 | + node.included_languages = channel.included_languages; |
319 | 339 | return node; |
320 | 340 | } |
321 | 341 | }) |
|
326 | 346 | store.commit('CORE_SET_ERROR', null); |
327 | 347 | store.commit('SET_PAGE_NAME', PageNames.LIBRARY); |
328 | 348 | set(rootNodesLoading, false); |
| 349 | + if (!get(routeChannelLanguage)) { |
| 350 | + // If we have no currently selected channel language, we should try to select one |
| 351 | + // based on the user's preferred language. |
| 352 | + const closestMatchChannelLanguage = get(channelLanguages)[0]; |
| 353 | + const channelLanguage = |
| 354 | + closestMatchChannelLanguage && |
| 355 | + getContentLangActive(closestMatchChannelLanguage) > 1 |
| 356 | + ? closestMatchChannelLanguage.id |
| 357 | + : _allChannelsFlag; |
| 358 | + router.replace({ |
| 359 | + query: { |
| 360 | + ...currentRoute().query, |
| 361 | + channelLanguage, |
| 362 | + }, |
| 363 | + }); |
| 364 | + } |
329 | 365 | } |
330 | 366 | }, |
331 | 367 | error => { |
|
387 | 423 | watch(() => props.deviceId, showLibrary); |
388 | 424 |
|
389 | 425 | watch(displayingSearchResults, () => { |
390 | | - if (!displayingSearchResults.value && !rootNodes.value.length) { |
| 426 | + if (!displayingSearchResults.value && !_rootNodes.value.length) { |
391 | 427 | showLibrary(); |
392 | 428 | } |
393 | 429 | }); |
394 | 430 |
|
| 431 | + const channelLanguages = computed(() => { |
| 432 | + return sortBy( |
| 433 | + uniqBy( |
| 434 | + get(_rootNodes).map(node => node.lang), |
| 435 | + 'id', |
| 436 | + ), |
| 437 | + l => -getContentLangActive(l), |
| 438 | + ); |
| 439 | + }); |
| 440 | +
|
| 441 | + const languageFilterOptions = computed(() => { |
| 442 | + return get(channelLanguages).map(lang => ({ |
| 443 | + label: lang.lang_name, |
| 444 | + value: lang.id, |
| 445 | + })); |
| 446 | + }); |
| 447 | +
|
| 448 | + const _allChannelsFlag = 'all'; |
| 449 | +
|
| 450 | + const currentChannelLanguage = computed({ |
| 451 | + get() { |
| 452 | + if (!get(routeChannelLanguage) || get(routeChannelLanguage) === _allChannelsFlag) { |
| 453 | + return {}; |
| 454 | + } |
| 455 | + return get(languageFilterOptions).find(o => o.value === get(routeChannelLanguage)) || {}; |
| 456 | + }, |
| 457 | + set(newValue) { |
| 458 | + const channelLanguage = newValue?.value || _allChannelsFlag; |
| 459 | + router.push({ |
| 460 | + query: { |
| 461 | + ...currentRoute().query, |
| 462 | + channelLanguage, |
| 463 | + }, |
| 464 | + }); |
| 465 | + }, |
| 466 | + }); |
| 467 | +
|
| 468 | + const rootNodes = computed(() => { |
| 469 | + if (isEmpty(get(currentChannelLanguage))) { |
| 470 | + return get(_rootNodes); |
| 471 | + } |
| 472 | + return get(_rootNodes).filter( |
| 473 | + node => |
| 474 | + node.lang.id === get(currentChannelLanguage).value || |
| 475 | + node.included_languages.includes(get(currentChannelLanguage).value), |
| 476 | + ); |
| 477 | + }); |
| 478 | +
|
395 | 479 | showLibrary(); |
396 | 480 |
|
397 | 481 | return { |
|
426 | 510 | isUserLoggedIn, |
427 | 511 | canManageContent, |
428 | 512 | isLearnerOnlyImport, |
| 513 | + channelLanguages, |
| 514 | + languageFilterOptions, |
| 515 | + currentChannelLanguage, |
429 | 516 | }; |
430 | 517 | }, |
431 | 518 | props: { |
|
0 commit comments