Skip to content

Commit 502b5ce

Browse files
authored
Merge pull request #5809 from AlexVelezLl/update-community-library-page
Update community library page
2 parents e70b1f9 + 4967d3e commit 502b5ce

13 files changed

Lines changed: 250 additions & 54 deletions

File tree

contentcuration/contentcuration/frontend/channelList/router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import StudioViewOnlyChannels from './views/Channel/StudioViewOnlyChannels';
66
import StudioCollectionsTable from './views/ChannelSet/StudioCollectionsTable';
77
import ChannelSetModal from './views/ChannelSet/ChannelSetModal';
88
import CatalogList from './views/Channel/CatalogList';
9-
import CommunityLibraryList from './views/Channel/CommunityLibraryList';
9+
import CommunityLibraryList from './views/Channel/CommunityLibraryList/index.vue';
1010
import { RouteNames } from './constants';
1111
import CatalogFAQ from './views/Channel/CatalogFAQ';
1212
import SubmissionDetailsModal from 'shared/views/communityLibrary/SubmissionDetailsModal/index.vue';

contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,18 @@
330330
getDropdownItems(channel) {
331331
const items = [];
332332
if (channel.source_url) {
333-
items.push({ label: this.$tr('goToWebsite'), icon: 'openNewTab', value: 'source-url' });
333+
items.push({
334+
label: this.$tr('goToWebsite'),
335+
icon: 'openNewTab',
336+
value: 'source-url',
337+
});
334338
}
335339
if (channel.demo_server_url) {
336-
items.push({ label: this.$tr('viewContent'), icon: 'openNewTab', value: 'demo-url' });
340+
items.push({
341+
label: this.$tr('viewContent'),
342+
icon: 'openNewTab',
343+
value: 'demo-url',
344+
});
337345
}
338346
return items;
339347
},
@@ -421,7 +429,7 @@
421429
},
422430
},
423431
$trs: {
424-
title: 'Content library',
432+
title: 'Kolibri library',
425433
resultsText: '{count, plural,\n =1 {# result found}\n other {# results found}}',
426434
selectChannels: 'Download a summary of selected channels',
427435
cancelButton: 'Cancel',

contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/CommunityLibraryFilters.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
v-model="countriesFilter"
1717
:label="countryLabel$()"
1818
:options="countryOptions"
19+
:disabled="disabled || !countryOptions.length"
1920
multiple
2021
clearable
2122
/>
@@ -24,6 +25,7 @@
2425
v-model="languagesFilter"
2526
:label="languagesLabel$()"
2627
:options="languageOptions"
28+
:disabled="disabled || !languageOptions.length"
2729
multiple
2830
clearable
2931
/>
@@ -32,6 +34,7 @@
3234
v-model="categoriesFilter"
3335
:label="categoriesLabel$()"
3436
:options="categoryOptions"
37+
:disabled="disabled || !categoryOptions.length"
3538
multiple
3639
clearable
3740
/>

contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/index.vue

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@
118118
/>
119119
</div>
120120
</div>
121-
<div class="list-wrapper">
121+
<div>
122122
<div class="results-header">
123123
<p
124-
v-if="!loading"
124+
v-if="!loading && activeFilters.length > 0"
125125
class="results-text"
126126
>
127127
{{ resultsText$({ count }) }}
@@ -203,7 +203,7 @@
203203

204204
<script>
205205
206-
import { computed, ref, watch } from 'vue';
206+
import { computed, onMounted, ref, watch } from 'vue';
207207
import { useRoute, useRouter } from 'vue-router/composables';
208208
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
209209
import { themeTokens } from 'kolibri-design-system/lib/styles/theme';
@@ -214,7 +214,7 @@
214214
import useCommunityChannelsFilters from './useCommunityChannelsFilters';
215215
import AboutCommunityLibraryModal from './AboutCommunityLibraryModal.vue';
216216
import ChannelTokenModal from 'shared/views/channel/ChannelTokenModal';
217-
import { listPublicChannels } from 'shared/data/public';
217+
import { listPublicChannels, getPublicChannelLabels } from 'shared/data/public';
218218
import useStore from 'shared/composables/useStore';
219219
import StudioChip from 'shared/views/StudioChip';
220220
import Pagination from 'shared/views/Pagination';
@@ -277,6 +277,8 @@
277277
} = communityChannelsStrings;
278278
const { copyChannelTokenAction$ } = commonStrings;
279279
280+
const availableLabels = ref(null);
281+
280282
const {
281283
countriesFilter,
282284
categoriesFilter,
@@ -285,9 +287,11 @@
285287
keywordInput,
286288
clearSearch,
287289
removeFilterValue,
288-
} = useCommunityChannelsFilters();
290+
} = useCommunityChannelsFilters({ availableLabels });
289291
290-
const loading = ref(false);
292+
const loadingChannels = ref(false);
293+
const loadingLabels = ref(false);
294+
const loading = computed(() => loadingChannels.value || loadingLabels.value);
291295
const loadError = ref(false);
292296
const channels = ref([]);
293297
const showFiltersSidePanel = ref(false);
@@ -328,7 +332,7 @@
328332
});
329333
330334
async function loadCommunityLibrary() {
331-
loading.value = true;
335+
loadingChannels.value = true;
332336
loadError.value = false;
333337
try {
334338
const response = await listPublicChannels(fetchQueryParams.value);
@@ -343,7 +347,7 @@
343347
totalPages.value = 0;
344348
store.dispatch('errors/handleAxiosError', error);
345349
} finally {
346-
loading.value = false;
350+
loadingChannels.value = false;
347351
}
348352
}
349353
@@ -439,6 +443,18 @@
439443
{ immediate: true, deep: true },
440444
);
441445
446+
onMounted(async () => {
447+
loadingLabels.value = true;
448+
try {
449+
availableLabels.value = await getPublicChannelLabels({ public: false });
450+
} catch {
451+
// Silently ignore: filter options will show all values rather than
452+
// restricting to those that have channels.
453+
} finally {
454+
loadingLabels.value = false;
455+
}
456+
});
457+
442458
return {
443459
windowIsSmall,
444460
windowBreakpoint,
@@ -543,18 +559,15 @@
543559
}
544560
545561
.content-container {
562+
max-width: 1080px;
546563
padding: 16px;
564+
margin: 0 auto;
547565
548566
p {
549567
margin: 8px 0;
550568
}
551569
}
552570
553-
.list-wrapper {
554-
max-width: 1080px;
555-
margin: 0 auto;
556-
}
557-
558571
.results-header {
559572
margin-bottom: 16px;
560573
}

contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/useCommunityChannelsFilters.js

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { computed, inject, provide } from 'vue';
1+
import { computed, inject, provide, unref } from 'vue';
22
import { useFilter } from 'shared/composables/useFilter';
33
import { useKeywordSearch } from 'shared/composables/useKeywordSearch';
44
import countriesUtil from 'shared/utils/countries';
@@ -19,43 +19,46 @@ const FILTERS_QUERY_PARAMS = Symbol('filtersQueryParams');
1919
const REMOVE_FILTER_VALUE = Symbol('removeFilterValue');
2020
const CLEAR_SEARCH = Symbol('clearSearch');
2121

22-
export default function useCommunityChannelsFilters() {
22+
export default function useCommunityChannelsFilters({ availableLabels } = {}) {
2323
const countryFilterMap = computed(() => {
2424
const [lang] = currentLanguage.split('-');
2525
const allCountries = countriesUtil.getNames(lang);
26+
const _availableLabels = unref(availableLabels);
27+
const availableCodes = _availableLabels?.countries
28+
? new Set(_availableLabels.countries.map(c => c.code))
29+
: null;
2630
return Object.fromEntries(
27-
Object.entries(allCountries).map(([code, name]) => [
28-
code,
29-
{
30-
label: name,
31-
params: { countries: code },
32-
},
33-
]),
31+
Object.entries(allCountries)
32+
.filter(([code]) => !availableCodes || availableCodes.has(code))
33+
.map(([code, name]) => [code, { label: name, params: { countries: code } }]),
3434
);
3535
});
3636

3737
const languageFilterMap = computed(() => {
38+
const _availableLabels = unref(availableLabels);
39+
const availableIds = _availableLabels?.languages
40+
? new Set(_availableLabels.languages.map(l => l.id))
41+
: null;
3842
return Object.fromEntries(
39-
LanguagesList.map(lang => [
40-
lang.id,
41-
{
42-
label: lang.readable_name,
43-
params: { languages: lang.id },
44-
},
45-
]),
43+
LanguagesList.filter(lang => !availableIds || availableIds.has(lang.id))
44+
.sort((a, b) => a.readable_name.localeCompare(b.readable_name))
45+
.map(lang => [lang.id, { label: lang.readable_name, params: { languages: lang.id } }]),
4646
);
4747
});
4848

4949
const categoryFilterMap = computed(() => {
5050
const sortedCategories = getSortedCategories();
51+
const _availableLabels = unref(availableLabels);
52+
const availableCategories = _availableLabels?.categories
53+
? new Set(_availableLabels.categories)
54+
: null;
5155
return Object.fromEntries(
52-
Object.entries(sortedCategories).map(([value, name]) => [
53-
value,
54-
{
55-
label: translateMetadataString(name),
56-
params: { categories: value },
57-
},
58-
]),
56+
Object.entries(sortedCategories)
57+
.filter(([value]) => !availableCategories || availableCategories.has(value))
58+
.map(([value, name]) => [
59+
value,
60+
{ label: translateMetadataString(name), params: { categories: value } },
61+
]),
5962
);
6063
});
6164

@@ -67,15 +70,10 @@ export default function useCommunityChannelsFilters() {
6770

6871
const {
6972
filter: languagesFilter,
70-
options: _languageOptions,
73+
options: languageOptions,
7174
fetchQueryParams: languagesFetchQueryParams,
7275
} = useFilter({ name: 'languages', filterMap: languageFilterMap, multi: true });
7376

74-
const languageOptions = computed(() => {
75-
// Sort language options alphabetically by label
76-
return _languageOptions.value.sort((a, b) => a.label.localeCompare(b.label));
77-
});
78-
7977
const {
8078
filter: categoriesFilter,
8179
options: categoryOptions,

contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe('CatalogList', () => {
126126
it('shows the visually hidden title and all channel cards in correct semantic structure', async () => {
127127
renderComponent();
128128

129-
const title = await screen.findByRole('heading', { name: /content library/i });
129+
const title = await screen.findByRole('heading', { name: /kolibri library/i });
130130
expect(title).toBeInTheDocument();
131131
expect(title.tagName).toBe('H1');
132132
expect(title).toHaveClass('visuallyhidden');

contentcuration/contentcuration/frontend/channelList/views/ChannelListIndex.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@
251251
title = this.translateConstant('bookmark');
252252
} else if (routeName === RouteNames.CHANNELS_EDITABLE) {
253253
title = this.translateConstant('edit');
254+
} else if (routeName === RouteNames.COMMUNITY_LIBRARY_ITEMS) {
255+
title = this.communityLibraryLabel$();
254256
}
255257
// Title changes for other routes are handled by other components, since
256258
// we can access $tr messages only from within the component.
@@ -264,7 +266,7 @@
264266
},
265267
$trs: {
266268
channelSets: 'Collections',
267-
catalog: 'Content Library',
269+
catalog: 'Kolibri Library',
268270
libraryTitle: 'Kolibri Content Library Catalog',
269271
frequentlyAskedQuestions: 'Frequently asked questions',
270272
},

contentcuration/contentcuration/frontend/shared/data/public.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ export function listPublicChannels(params = {}) {
165165
return client.get(urls.publicchannel_list(), { params }).then(response => response.data);
166166
}
167167

168+
/**
169+
* Get available filter label options for the public channel list.
170+
* Returns an object with available values for each filterable field
171+
* (categories, languages, countries).
172+
*
173+
* @param {Object} params
174+
* @return {Promise<{
175+
* categories: string[],
176+
* languages: Array<{id: string, lang_name: string}>,
177+
* countries: Array<{code: string, name: string}>
178+
* }>}
179+
*/
180+
export function getPublicChannelLabels(params = {}) {
181+
return client.get(urls.publicchannel_labels(), { params }).then(response => response.data);
182+
}
183+
168184
/**
169185
* Get a content node from the public API
170186
* @param {String} nodeId

contentcuration/contentcuration/frontend/shared/mixins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const fileStatusMixin = {
8989
export const constantStrings = createTranslator('ConstantStrings', {
9090
[ChannelListTypes.EDITABLE]: 'My channels',
9191
[ChannelListTypes.VIEW_ONLY]: 'View-only',
92-
[ChannelListTypes.PUBLIC]: 'Content library',
92+
[ChannelListTypes.PUBLIC]: 'Kolibri library',
9393
[ChannelListTypes.STARRED]: 'Starred',
9494
do_all: 'Goal: 100% correct',
9595
num_correct_in_a_row_10: 'Goal: 10 in a row',

contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin
195195
'Description of warning shown in the "Submit to Community Library" panel when the channel is not published',
196196
},
197197
publicWarningTitle: {
198-
message: 'This channel is currently public in the Content Library.',
198+
message: 'This channel is currently public in the Kolibri Library.',
199199
context:
200200
'Title of warning shown in the "Submit to Community Library" panel when the channel is public',
201201
},

0 commit comments

Comments
 (0)