Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

My downloads page #10137

Merged
merged 17 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import FilterTextbox from 'kolibri.coreVue.components.FilterTextbox';
import UserTable from 'kolibri.coreVue.components.UserTable';
import PaginatedListContainerWithBackend from './PaginatedListContainerWithBackend';
import PaginatedListContainerWithBackend from 'kolibri-common/components/PaginatedListContainerWithBackend';
import SelectionBottomBar from './SelectionBottomBar';

export default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
import FilterTextbox from 'kolibri.coreVue.components.FilterTextbox';
import UserTable from 'kolibri.coreVue.components.UserTable';
import cloneDeep from 'lodash/cloneDeep';
import PaginatedListContainerWithBackend from '../PaginatedListContainerWithBackend';
import PaginatedListContainerWithBackend from 'kolibri-common/components/PaginatedListContainerWithBackend';
import { Modals } from '../../constants';
import FacilityAppBarPage from '../FacilityAppBarPage';
import ResetUserPasswordModal from './ResetUserPasswordModal';
Expand Down
40 changes: 39 additions & 1 deletion kolibri/plugins/learn/assets/src/composables/useContentLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { get } from '@vueuse/core';
import isEmpty from 'lodash/isEmpty';
import pick from 'lodash/pick';
import { computed, getCurrentInstance } from 'kolibri.lib.vueCompositionApi';
import { PageNames } from '../constants';
import { ExternalPagePaths, PageNames } from '../constants';

export default function useContentLink(store) {
// Get store reference from the curent instance
Expand Down Expand Up @@ -47,6 +47,29 @@ export default function useContentLink(store) {
return _makeLink(id, isResource, query);
}

function genExternalContentURLBackLinkCurrentPage(id) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didnt find a better solution for getting the url of the content renderer to move from my_downloads to learn :/. Due to the fact that they are differents SPA they dont have the same router object

Copy link
Member

@rtibbles rtibbles Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems tractable, if a little complex. Another option here would be to create a content preview page within the downloads SPA, but this can work for now, especially as they both live in the same plugin, so cannot be deactivated separately.

It's possible that some of the work that @marcellamaki is doing in her app bar PR could be reused here to simplify this slightly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we may be able to repurpose that here, and once this is merged, I can also do a follow up to change the side navigation item to match the new CoreMenuOption API @rtibbles

const pathname = window.location.pathname;
const learnIndex = pathname.indexOf('/learn');
const base = pathname.slice(0, learnIndex) + '/learn/#';
if (!route) {
return base;
}
const oldQuery = get(route).query || {};
const query = {
prevName: get(route).name,
};
if (!isEmpty(oldQuery)) {
query.prevQuery = encodeURI(JSON.stringify(oldQuery));
}
const params = get(route).params;
if (!isEmpty(params)) {
query.prevParams = encodeURI(JSON.stringify(params));
}
const path = `/topics/c/${id}`;

return `${base}${path}?${new URLSearchParams(query)}`;
}

/**
* A function to generate a VueRouter link object that links to
* either a resource or a topic, and copies current query parameters
Expand Down Expand Up @@ -90,9 +113,24 @@ export default function useContentLink(store) {
};
});

function genExternalBackURL() {
const pathname = window.location.pathname;
const learnIndex = pathname.indexOf('/learn');
const base = pathname.slice(0, learnIndex) + '/learn';
const backValue = get(back);
if (!backValue) {
return base;
}
const query = backValue.query ? `#/?${new URLSearchParams(backValue.query)}` : '';
const path = ExternalPagePaths[backValue.name];
return `${base}${path}${query}`;
}

return {
genContentLinkBackLinkCurrentPage,
genContentLinkKeepCurrentBackLink,
genExternalContentURLBackLinkCurrentPage,
genExternalBackURL,
back,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* A composable function containing logic related to download requests
*/

import { getCurrentInstance, reactive } from 'kolibri.lib.vueCompositionApi';
import { getCurrentInstance, reactive, ref } from 'kolibri.lib.vueCompositionApi';
import Vue from 'kolibri.lib.vue';
import { createTranslator } from 'kolibri.utils.i18n';
import { set } from '@vueuse/core';
Expand All @@ -23,13 +23,20 @@ const translator = createTranslator('DownloadRequests', {
});

// The reactive is defined in the outer scope so it can be used as a shared store
const downloadRequestMap = reactive({});
const downloadRequestMap = reactive({
downloads: {},
totalPageNumber: 0,
totalDownloads: 0,
});

export default function useDownloadRequests(store) {
store = store || getCurrentInstance().proxy.$store;
function fetchUserDownloadRequests() {
function fetchUserDownloadRequests(params) {
const { page, pageSize } = params;
const loading = ref(true);
const dummyDownloadRequests = [
{
id: '2ea9bda8703241be89b5b9fd87f88815',
user_id: store.getters.currentUserId,
reason: 'USER_INITIATED',
facility_id: store.getters.currentFacilityId,
Expand All @@ -42,10 +49,57 @@ export default function useDownloadRequests(store) {
},
node_id: '2ea9bda8703241be89b5b9fd87f88815',
},
{
id: '9e53d545aaf44c3787a29a34b189c56a',
user_id: store.getters.currentUserId,
reason: 'USER_INITIATED',
facility_id: store.getters.currentFacilityId,
status: 'QUEUED',
date_added: new Date(),
resource_metadata: {
title: 'PDF 1 page',
file_size: 3113580,
learning_activities: ['wA01urpi'],
},
node_id: '9e53d545aaf44c3787a29a34b189c56a',
},
];
for (const req of dummyDownloadRequests) {
set(downloadRequestMap, req.node_id, req);
}
setTimeout(() => {
set(downloadRequestMap, 'downloads', {});
for (let i = 0; i < pageSize; i++) {
const index = (page - 1) * pageSize + i;
if (index >= dummyDownloadRequests.length) {
break;
}
set(
downloadRequestMap.downloads,
dummyDownloadRequests[index].node_id,
dummyDownloadRequests[index]
);
}
set(
downloadRequestMap,
'totalPageNumber',
Math.ceil(dummyDownloadRequests.length / pageSize)
);
set(downloadRequestMap, 'totalDownloads', dummyDownloadRequests.length);
set(loading, false);
}, 500);
return loading;
}

function fetchDownloadsStorageInfo() {
const loading = ref(true);
const storageInfo = ref(null);
const dummyStorageInfo = {
freeDiskSize: 13340000000,
myDownloadsSize: 23200000,
};
setTimeout(() => {
set(storageInfo, dummyStorageInfo);
set(loading, false);
}, 600);
return { loading, storageInfo };
}

function navigateToDownloads() {}
Expand Down Expand Up @@ -80,14 +134,24 @@ export default function useDownloadRequests(store) {

function removeDownloadRequest(content) {
console.log(`requested removal of ${content.id}`);
Vue.delete(downloadRequestMap, content.id);
Vue.delete(downloadRequestMap.downloads, content.id);
return Promise.resolve();
}

function removeDownloadsRequest(contentList) {
console.log(`requested removal of ${contentList.length} items`);
contentList.forEach(content => {
Vue.delete(downloadRequestMap.downloads, content.id);
});
return Promise.resolve();
}

return {
fetchUserDownloadRequests,
fetchDownloadsStorageInfo,
downloadRequestMap,
addDownloadRequest,
removeDownloadRequest,
removeDownloadsRequest,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export default function useLearningActivities(contentNode) {
[LearningActivities.EXPLORE]: coreStrings.$tr('explore'),
};

const _LearningActivityToIconMap = {
[LearningActivities.CREATE]: 'createSolid',
[LearningActivities.LISTEN]: 'listenSolid',
[LearningActivities.REFLECT]: 'reflectSolid',
[LearningActivities.PRACTICE]: 'practiceSolid',
[LearningActivities.READ]: 'readSolid',
[LearningActivities.WATCH]: 'watchSolid',
[LearningActivities.EXPLORE]: 'interactSolid',
};

/**
* @returns {Boolean} Does the content node have exactly
* one learning activity associated with it?
Expand Down Expand Up @@ -130,6 +140,14 @@ export default function useLearningActivities(contentNode) {
return _LearningActivityToLabelMap[learningActivity];
}

/**
* @param {String} learningActivity A learning activity constant
* @returns {String} An icon for the learning activity
*/
function getLearningActivityIcon(learningActivity) {
return _LearningActivityToIconMap[learningActivity];
}

return {
ReferenceLabel,
hasSingleActivity,
Expand All @@ -141,5 +159,6 @@ export default function useLearningActivities(contentNode) {
durationInSeconds,
durationEstimation,
getLearningActivityLabel,
getLearningActivityIcon,
};
}
8 changes: 8 additions & 0 deletions kolibri/plugins/learn/assets/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export const PageNames = {
EXPLORE_LIBRARIES: 'EXPLORE_LIBRARIES',
};

export const ExternalPageNames = {
MY_DOWNLOADS: 'MY_DOWNLOADS',
};

export const ExternalPagePaths = {
[ExternalPageNames.MY_DOWNLOADS]: '/my-downloads',
};

// switch between modes
export const PageModes = {
TOPICS: 'TOPICS',
Expand Down
21 changes: 21 additions & 0 deletions kolibri/plugins/learn/assets/src/my_downloads/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PageRoot from 'kolibri.coreVue.components.PageRoot';
import routes from './routes';
import pluginModule from './modules/pluginModule';
import KolibriApp from 'kolibri_app';

class MyDownloadsModule extends KolibriApp {
get routes() {
return routes;
}
get RootVue() {
return PageRoot;
}
get pluginModule() {
return pluginModule;
}
ready() {
super.ready();
}
}

export default new MyDownloadsModule();
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
state() {
return {};
},
actions: {},
mutations: {},
};
18 changes: 18 additions & 0 deletions kolibri/plugins/learn/assets/src/my_downloads/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import store from 'kolibri.coreVue.vuex.store';
import redirectBrowser from 'kolibri.utils.redirectBrowser';
import MyDownloadsPage from './views/MyDownloads';

export default [
{
path: '/',
name: 'MY_DOWNLOADS',
component: MyDownloadsPage,
beforeEnter(to, from, next) {
if (!store.getters.isUserLoggedIn) {
redirectBrowser();
} else {
next();
}
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>

<KModal
:title="coreString('removeFromLibrary')"
:submitText="coreString('removeAction')"
:cancelText="coreString('cancelAction')"
@submit="removeResources"
@cancel="$emit('cancel')"
>
{{ message }}
</KModal>

</template>


<script>

import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';

export default {
name: 'ConfirmationDeleteModal',
mixins: [commonCoreStrings],
props: {
resourcesToDelete: {
type: Array,
required: true,
},
},
computed: {
message() {
if (this.resourcesToDelete.length === 1) {
return this.coreString('removeResourceText');
}
return this.coreString('removeResourcesText');
},
},
methods: {
removeResources() {
this.$emit('success');
},
},
};

</script>


<style lang="scss" scoped>

@import '~kolibri-design-system/lib/styles/definitions';

</style>
Loading