diff --git a/kolibri/core/assets/src/core-app/apiSpec.js b/kolibri/core/assets/src/core-app/apiSpec.js index 064f09a65f3..7b2ffaa9f20 100644 --- a/kolibri/core/assets/src/core-app/apiSpec.js +++ b/kolibri/core/assets/src/core-app/apiSpec.js @@ -65,6 +65,7 @@ import redirectBrowser from '../utils/redirectBrowser'; import * as licenseTranslations from '../utils/licenseTranslations'; import bytesForHumans from '../utils/bytesForHumans'; import UserType from '../utils/UserType'; +import * as syncTaskUtils from '../utils/syncTaskUtils'; import samePageCheckGenerator from '../utils/samePageCheckGenerator'; import Backdrop from '../views/Backdrop'; import CoreSnackbar from '../views/CoreSnackbar'; @@ -245,6 +246,7 @@ export default { serverClock, shuffled, sortLanguages, + syncTaskUtils, UserType, validators, coreStrings, diff --git a/kolibri/core/assets/src/mixins/commonSyncElements.js b/kolibri/core/assets/src/mixins/commonSyncElements.js index 99fc2888ac0..aa1ebd4388f 100644 --- a/kolibri/core/assets/src/mixins/commonSyncElements.js +++ b/kolibri/core/assets/src/mixins/commonSyncElements.js @@ -25,7 +25,7 @@ const syncStrings = createTranslator('CommonSyncStrings', { context: 'Label for a button that opens a menu to save a new network address.', }, selectFacilityTitle: { - message: 'Select facility', + message: 'Select learning facility', context: 'Title of the modal window where the user selects a facility to import from the source device, if there are multiple facilities available to import.', }, diff --git a/kolibri/plugins/device/assets/src/views/syncTaskUtils.js b/kolibri/core/assets/src/utils/syncTaskUtils.js similarity index 60% rename from kolibri/plugins/device/assets/src/views/syncTaskUtils.js rename to kolibri/core/assets/src/utils/syncTaskUtils.js index 51027ab820d..4b99a97e34f 100644 --- a/kolibri/plugins/device/assets/src/views/syncTaskUtils.js +++ b/kolibri/core/assets/src/utils/syncTaskUtils.js @@ -1,7 +1,45 @@ -import coreStrings from 'kolibri.coreVue.mixins.commonCoreStrings'; -import taskStrings from 'kolibri.coreVue.mixins.commonTaskStrings'; -import bytesForHumans from 'kolibri.utils.bytesForHumans'; -import { TaskStatuses, TaskTypes } from '../constants'; +import commonCoreStrings from '../mixins/commonCoreStrings'; +import { getTaskString } from '../mixins/taskStrings'; +import bytesForHumans from '../utils/bytesForHumans'; + +export const TaskTypes = { + REMOTECHANNELIMPORT: 'kolibri.core.content.tasks.remotechannelimport', + REMOTECONTENTIMPORT: 'kolibri.core.content.tasks.remotecontentimport', + REMOTEIMPORT: 'kolibri.core.content.tasks.remoteimport', + DISKCHANNELIMPORT: 'kolibri.core.content.tasks.diskchannelimport', + DISKCONTENTIMPORT: 'kolibri.core.content.tasks.diskcontentimport', + DISKIMPORT: 'kolibri.core.content.tasks.diskimport', + DISKCONTENTEXPORT: 'kolibri.core.content.tasks.diskcontentexport', + DISKEXPORT: 'kolibri.core.content.tasks.diskexport', + DELETECHANNEL: 'kolibri.core.content.tasks.deletechannel', + UPDATECHANNEL: 'kolibri.core.content.tasks.updatechannel', + REMOTECHANNELDIFFSTATS: 'kolibri.core.content.tasks.remotechanneldiffstats', + LOCALCHANNELDIFFSTATS: 'kolibri.core.content.tasks.localchanneldiffstats', + SYNCDATAPORTAL: 'kolibri.core.auth.tasks.dataportalsync', + SYNCPEERFULL: 'kolibri.core.auth.tasks.peerfacilitysync', + SYNCPEERPULL: 'kolibri.core.auth.tasks.peerfacilityimport', + DELETEFACILITY: 'kolibri.core.auth.tasks.deletefacility', +}; + +// identical to facility constants.js +export const TaskStatuses = Object.freeze({ + IN_PROGRESS: 'INPROGRESS', + COMPLETED: 'COMPLETED', + FAILED: 'FAILED', + PENDING: 'PENDING', + RUNNING: 'RUNNING', + QUEUED: 'QUEUED', + SCHEDULED: 'SCHEDULED', + CANCELED: 'CANCELED', + CANCELING: 'CANCELING', +}); + +export const TransferTypes = { + LOCALEXPORT: 'localexport', + LOCALIMPORT: 'localimport', + PEERIMPORT: 'peerimport', + REMOTEIMPORT: 'remoteimport', +}; export const SyncTaskStatuses = { SESSION_CREATION: 'SESSION_CREATION', @@ -18,8 +56,7 @@ export const SyncTaskStatuses = { FAILED: 'FAILED', }; -const { getTaskString } = taskStrings.methods; -const { coreString } = coreStrings.methods; +const { coreString } = commonCoreStrings.methods; const syncTaskStatusToStepMap = { [SyncTaskStatuses.SESSION_CREATION]: 1, @@ -31,24 +68,25 @@ const syncTaskStatusToStepMap = { [SyncTaskStatuses.REMOTE_DEQUEUING]: 7, }; +// getTaskString is wrapped in an arrow func to avoid evaluation before i18n is ready const genericStatusToDescriptionMap = { - [TaskStatuses.PENDING]: getTaskString('taskWaitingStatus'), - [TaskStatuses.QUEUED]: getTaskString('taskWaitingStatus'), - [TaskStatuses.COMPLETED]: getTaskString('taskFinishedStatus'), - [TaskStatuses.CANCELED]: getTaskString('taskCanceledStatus'), - [TaskStatuses.CANCELING]: getTaskString('taskCancelingStatus'), - [TaskStatuses.FAILED]: getTaskString('taskFailedStatus'), + [TaskStatuses.PENDING]: () => getTaskString('taskWaitingStatus'), + [TaskStatuses.QUEUED]: () => getTaskString('taskWaitingStatus'), + [TaskStatuses.COMPLETED]: () => getTaskString('taskFinishedStatus'), + [TaskStatuses.CANCELED]: () => getTaskString('taskCanceledStatus'), + [TaskStatuses.CANCELING]: () => getTaskString('taskCancelingStatus'), + [TaskStatuses.FAILED]: () => getTaskString('taskFailedStatus'), }; export const syncStatusToDescriptionMap = { ...genericStatusToDescriptionMap, - [SyncTaskStatuses.SESSION_CREATION]: getTaskString('establishingConnectionStatus'), - [SyncTaskStatuses.REMOTE_QUEUING]: getTaskString('remotelyPreparingDataStatus'), - [SyncTaskStatuses.PULLING]: getTaskString('receivingDataStatus'), - [SyncTaskStatuses.LOCAL_DEQUEUING]: getTaskString('locallyIntegratingDataStatus'), - [SyncTaskStatuses.LOCAL_QUEUING]: getTaskString('locallyPreparingDataStatus'), - [SyncTaskStatuses.PUSHING]: getTaskString('sendingDataStatus'), - [SyncTaskStatuses.REMOTE_DEQUEUING]: getTaskString('remotelyIntegratingDataStatus'), + [SyncTaskStatuses.SESSION_CREATION]: () => getTaskString('establishingConnectionStatus'), + [SyncTaskStatuses.REMOTE_QUEUING]: () => getTaskString('remotelyPreparingDataStatus'), + [SyncTaskStatuses.PULLING]: () => getTaskString('receivingDataStatus'), + [SyncTaskStatuses.LOCAL_DEQUEUING]: () => getTaskString('locallyIntegratingDataStatus'), + [SyncTaskStatuses.LOCAL_QUEUING]: () => getTaskString('locallyPreparingDataStatus'), + [SyncTaskStatuses.PUSHING]: () => getTaskString('sendingDataStatus'), + [SyncTaskStatuses.REMOTE_DEQUEUING]: () => getTaskString('remotelyIntegratingDataStatus'), }; function formatNameWithId(name, id) { @@ -85,7 +123,7 @@ export function syncFacilityTaskDisplayInfo(task) { const statusDescription = syncStatusToDescriptionMap[task.extra_metadata.sync_state] || syncStatusToDescriptionMap[task.status] || - getTaskString('taskUnknownStatus'); + (() => getTaskString('taskUnknownStatus')); if (task.status === TaskStatuses.COMPLETED) { statusMsg = getTaskString('taskFinishedStatus'); @@ -93,12 +131,12 @@ export function syncFacilityTaskDisplayInfo(task) { statusMsg = getTaskString('syncStepAndDescription', { step: syncStep, total: task.type === TaskTypes.SYNCPEERPULL ? PULLSTEPS : PUSHPULLSTEPS, - description: statusDescription, + description: statusDescription(), }); } else { if (task.type === TaskTypes.SYNCLOD && task.status === TaskStatuses.FAILED) - statusMsg = `${statusDescription}: ${task.exception}`; - else statusMsg = statusDescription; + statusMsg = `${statusDescription()}: ${task.exception}`; + else statusMsg = statusDescription(); } if (task.status === TaskStatuses.COMPLETED) { @@ -127,7 +165,7 @@ export function syncFacilityTaskDisplayInfo(task) { export const removeStatusToDescriptionMap = { ...genericStatusToDescriptionMap, - [TaskStatuses.RUNNING]: getTaskString('removingFacilityStatus'), + [TaskStatuses.RUNNING]: () => getTaskString('removingFacilityStatus'), }; // Consolidates logic on how Remove-Facility Tasks should be displayed @@ -137,7 +175,7 @@ export function removeFacilityTaskDisplayInfo(task) { task.extra_metadata.facility ); const statusDescription = - removeStatusToDescriptionMap[task.status] || getTaskString('taskUnknownStatus'); + removeStatusToDescriptionMap[task.status]() || getTaskString('taskUnknownStatus'); return { headingMsg: getTaskString('removeFacilityTaskLabel', { facilityName }), diff --git a/kolibri/core/assets/src/views/language-switcher/LanguageSwitcherModal.vue b/kolibri/core/assets/src/views/language-switcher/LanguageSwitcherModal.vue index 59fc720fd35..b27aeebc48f 100644 --- a/kolibri/core/assets/src/views/language-switcher/LanguageSwitcherModal.vue +++ b/kolibri/core/assets/src/views/language-switcher/LanguageSwitcherModal.vue @@ -1,34 +1,42 @@ @@ -38,10 +46,12 @@ import { currentLanguage } from 'kolibri.utils.i18n'; import responsiveWindowMixin from 'kolibri.coreVue.mixins.responsiveWindowMixin'; import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings'; + import FocusTrap from 'kolibri.coreVue.components.FocusTrap'; import languageSwitcherMixin from './mixin'; export default { name: 'LanguageSwitcherModal', + components: { FocusTrap }, mixins: [commonCoreStrings, languageSwitcherMixin, responsiveWindowMixin], data() { return { @@ -57,6 +67,12 @@ }, }, methods: { + focusFirstEl() { + this.$refs.languageItem[0].focus(); + }, + focusLastEl() { + this.$refs.languageItem[this.$refs.languageItem.length - 1].focus(); + }, setLang() { if (currentLanguage === this.selectedLanguage) { this.cancel(); diff --git a/kolibri/core/assets/src/views/sync/FacilityAdminCredentialsForm.vue b/kolibri/core/assets/src/views/sync/FacilityAdminCredentialsForm.vue index 44a71f60bd1..26160b8c901 100644 --- a/kolibri/core/assets/src/views/sync/FacilityAdminCredentialsForm.vue +++ b/kolibri/core/assets/src/views/sync/FacilityAdminCredentialsForm.vue @@ -1,6 +1,6 @@ diff --git a/kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanel.vue b/kolibri/core/assets/src/views/sync/FacilityTaskPanel.vue similarity index 97% rename from kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanel.vue rename to kolibri/core/assets/src/views/sync/FacilityTaskPanel.vue index 500c42f5726..7bcaf518df7 100644 --- a/kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanel.vue +++ b/kolibri/core/assets/src/views/sync/FacilityTaskPanel.vue @@ -25,8 +25,8 @@ syncFacilityTaskDisplayInfo, removeFacilityTaskDisplayInfo, importFacilityTaskDisplayInfo, - } from '../syncTaskUtils'; - import { TaskTypes } from '../../constants'; + TaskTypes, + } from '../../utils/syncTaskUtils'; import FacilityTaskPanelDetails from './FacilityTaskPanelDetails'; const indeterminateSyncStatuses = [ diff --git a/kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanelDetails.vue b/kolibri/core/assets/src/views/sync/FacilityTaskPanelDetails.vue similarity index 98% rename from kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanelDetails.vue rename to kolibri/core/assets/src/views/sync/FacilityTaskPanelDetails.vue index 569310eb292..8edfc825169 100644 --- a/kolibri/plugins/device/assets/src/views/FacilitiesPage/FacilityTaskPanelDetails.vue +++ b/kolibri/core/assets/src/views/sync/FacilityTaskPanelDetails.vue @@ -108,7 +108,7 @@ import responsiveWindowMixin from 'kolibri.coreVue.mixins.responsiveWindowMixin'; import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings'; import commonTaskStrings from 'kolibri.coreVue.mixins.commonTaskStrings'; - import { TaskStatuses, TaskTypes } from '../../constants'; + import { TaskStatuses, TaskTypes } from '../../utils/syncTaskUtils'; export default { name: 'FacilityTaskPanelDetails', diff --git a/kolibri/core/assets/src/views/sync/SelectAddressModalGroup/SelectAddressForm.vue b/kolibri/core/assets/src/views/sync/SelectAddressModalGroup/SelectAddressForm.vue index 75e0e23c027..fbb0fdf5c2c 100644 --- a/kolibri/core/assets/src/views/sync/SelectAddressModalGroup/SelectAddressForm.vue +++ b/kolibri/core/assets/src/views/sync/SelectAddressModalGroup/SelectAddressForm.vue @@ -30,14 +30,6 @@ /> - - + + + @@ -231,12 +232,12 @@ }; }, submitDisabled() { - return ( + return Boolean( this.selectedAddressId === '' || - this.fetchingAddresses & !this.filterLODAvailable || - this.deletingAddress || - this.discoveryFailed || - this.availableAddressIds.length === 0 + this.fetchingAddresses & !this.filterLODAvailable || + this.deletingAddress || + this.discoveryFailed || + this.availableAddressIds.length === 0 ); }, newAddressButtonDisabled() { @@ -361,7 +362,7 @@ + diff --git a/kolibri/plugins/setup_wizard/assets/src/views/OnboardingStepBase.vue b/kolibri/plugins/setup_wizard/assets/src/views/OnboardingStepBase.vue index 741ec25b708..0f23ebce737 100644 --- a/kolibri/plugins/setup_wizard/assets/src/views/OnboardingStepBase.vue +++ b/kolibri/plugins/setup_wizard/assets/src/views/OnboardingStepBase.vue @@ -4,6 +4,7 @@
@@ -44,7 +45,7 @@

{{ title }}

-

+

{{ description }}

@@ -70,11 +71,13 @@ :text="coreString('goBackAction')" appearance="flat-button" :primary="false" + :disabled="navDisabled" @click="wizardService.send('BACK')" /> @@ -85,6 +88,7 @@ class="mobile-continue-button" :text="coreString('continueAction')" :primary="true" + :disabled="navDisabled" @click="$emit('continue')" />
@@ -120,6 +124,10 @@ type: Boolean, default: false, }, + navDisabled: { + type: Boolean, + default: false, + }, title: { type: String, default: null, @@ -139,6 +147,15 @@ return availableLanguages[currentLanguage]; }, }, + methods: { + /* If the user is focused on a form element and hits enter, continue */ + handleEnterKey(e) { + e.preventDefault(); + if (!this.navDisabled & (e.target.tagName === 'INPUT')) { + this.$emit('continue'); + } + }, + }, }; diff --git a/kolibri/plugins/setup_wizard/assets/src/views/importFacility/ImportAuthentication.vue b/kolibri/plugins/setup_wizard/assets/src/views/importFacility/ImportAuthentication.vue new file mode 100644 index 00000000000..bfbdf92b2d3 --- /dev/null +++ b/kolibri/plugins/setup_wizard/assets/src/views/importFacility/ImportAuthentication.vue @@ -0,0 +1,91 @@ + + + + + + + diff --git a/kolibri/plugins/setup_wizard/assets/src/views/importFacility/LoadingTaskPage.vue b/kolibri/plugins/setup_wizard/assets/src/views/importFacility/LoadingTaskPage.vue index 29b4210572b..d9144d1a74d 100644 --- a/kolibri/plugins/setup_wizard/assets/src/views/importFacility/LoadingTaskPage.vue +++ b/kolibri/plugins/setup_wizard/assets/src/views/importFacility/LoadingTaskPage.vue @@ -1,7 +1,9 @@ - +