commit
771ee90d6a
|
@ -130,11 +130,6 @@
|
||||||
<param name="android-package" value="org.apache.cordova.geolocation.Geolocation" />
|
<param name="android-package" value="org.apache.cordova.geolocation.Geolocation" />
|
||||||
</feature>
|
</feature>
|
||||||
</config-file>
|
</config-file>
|
||||||
<config-file parent="/*" target="res/xml/config.xml">
|
|
||||||
<feature name="Globalization">
|
|
||||||
<param name="android-package" value="org.apache.cordova.globalization.Globalization" />
|
|
||||||
</feature>
|
|
||||||
</config-file>
|
|
||||||
<config-file parent="/*" target="res/xml/config.xml">
|
<config-file parent="/*" target="res/xml/config.xml">
|
||||||
<feature name="InAppBrowser">
|
<feature name="InAppBrowser">
|
||||||
<param name="android-package" value="org.apache.cordova.inappbrowser.InAppBrowser" />
|
<param name="android-package" value="org.apache.cordova.inappbrowser.InAppBrowser" />
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
"cordova-plugin-file": "6.0.2",
|
"cordova-plugin-file": "6.0.2",
|
||||||
"cordova-plugin-file-opener2": "3.0.5",
|
"cordova-plugin-file-opener2": "3.0.5",
|
||||||
"cordova-plugin-geolocation": "4.1.0",
|
"cordova-plugin-geolocation": "4.1.0",
|
||||||
"cordova-plugin-globalization": "1.11.0",
|
|
||||||
"cordova-plugin-ionic-keyboard": "2.2.0",
|
"cordova-plugin-ionic-keyboard": "2.2.0",
|
||||||
"cordova-plugin-media": "5.0.4",
|
"cordova-plugin-media": "5.0.4",
|
||||||
"cordova-plugin-media-capture": "3.0.3",
|
"cordova-plugin-media-capture": "3.0.3",
|
||||||
|
@ -11389,18 +11388,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cordova-plugin-globalization": {
|
|
||||||
"version": "1.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-globalization/-/cordova-plugin-globalization-1.11.0.tgz",
|
|
||||||
"integrity": "sha1-6sMVgQAphJOvowvolA5pj2HvvP4=",
|
|
||||||
"engines": {
|
|
||||||
"cordovaDependencies": {
|
|
||||||
"2.0.0": {
|
|
||||||
"cordova": ">100"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cordova-plugin-ionic-keyboard": {
|
"node_modules/cordova-plugin-ionic-keyboard": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
|
||||||
|
@ -40148,11 +40135,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-4.1.0.tgz",
|
||||||
"integrity": "sha512-y5io/P10xGMxSn2KEqfv/fExK47eA1pmSonJdmDqDsaSADV9JpgdPx0mUSA08+5pzma/OS9R0LoODeDPx7Jvjg=="
|
"integrity": "sha512-y5io/P10xGMxSn2KEqfv/fExK47eA1pmSonJdmDqDsaSADV9JpgdPx0mUSA08+5pzma/OS9R0LoODeDPx7Jvjg=="
|
||||||
},
|
},
|
||||||
"cordova-plugin-globalization": {
|
|
||||||
"version": "1.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-globalization/-/cordova-plugin-globalization-1.11.0.tgz",
|
|
||||||
"integrity": "sha1-6sMVgQAphJOvowvolA5pj2HvvP4="
|
|
||||||
},
|
|
||||||
"cordova-plugin-ionic-keyboard": {
|
"cordova-plugin-ionic-keyboard": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
|
||||||
|
|
|
@ -100,7 +100,6 @@
|
||||||
"cordova-plugin-file": "6.0.2",
|
"cordova-plugin-file": "6.0.2",
|
||||||
"cordova-plugin-file-opener2": "3.0.5",
|
"cordova-plugin-file-opener2": "3.0.5",
|
||||||
"cordova-plugin-geolocation": "4.1.0",
|
"cordova-plugin-geolocation": "4.1.0",
|
||||||
"cordova-plugin-globalization": "1.11.0",
|
|
||||||
"cordova-plugin-ionic-keyboard": "2.2.0",
|
"cordova-plugin-ionic-keyboard": "2.2.0",
|
||||||
"cordova-plugin-media": "5.0.4",
|
"cordova-plugin-media": "5.0.4",
|
||||||
"cordova-plugin-media-capture": "3.0.3",
|
"cordova-plugin-media-capture": "3.0.3",
|
||||||
|
@ -236,7 +235,6 @@
|
||||||
"ANDROIDX_VERSION": "1.0.0",
|
"ANDROIDX_VERSION": "1.0.0",
|
||||||
"ANDROIDX_APPCOMPAT_VERSION": "1.3.1"
|
"ANDROIDX_APPCOMPAT_VERSION": "1.3.1"
|
||||||
},
|
},
|
||||||
"cordova-plugin-globalization": {},
|
|
||||||
"@moodlehq/cordova-plugin-file-transfer": {},
|
"@moodlehq/cordova-plugin-file-transfer": {},
|
||||||
"cordova-plugin-prevent-override": {},
|
"cordova-plugin-prevent-override": {},
|
||||||
"cordova-plugin-androidx-adapter": {}
|
"cordova-plugin-androidx-adapter": {}
|
||||||
|
|
|
@ -1460,6 +1460,7 @@
|
||||||
"core.block.tour_navigation_dashboard_content": "tool_usertours",
|
"core.block.tour_navigation_dashboard_content": "tool_usertours",
|
||||||
"core.block.tour_navigation_dashboard_title": "tool_usertours",
|
"core.block.tour_navigation_dashboard_title": "tool_usertours",
|
||||||
"core.browser": "local_moodlemobileapp",
|
"core.browser": "local_moodlemobileapp",
|
||||||
|
"core.calculating": "local_moodlemobileapp",
|
||||||
"core.cancel": "moodle",
|
"core.cancel": "moodle",
|
||||||
"core.cannotconnect": "local_moodlemobileapp",
|
"core.cannotconnect": "local_moodlemobileapp",
|
||||||
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
||||||
|
@ -1680,6 +1681,7 @@
|
||||||
"core.editor.underline": "atto_underline/pluginname",
|
"core.editor.underline": "atto_underline/pluginname",
|
||||||
"core.editor.unorderedlist": "atto_unorderedlist/pluginname",
|
"core.editor.unorderedlist": "atto_unorderedlist/pluginname",
|
||||||
"core.emptysplit": "local_moodlemobileapp",
|
"core.emptysplit": "local_moodlemobileapp",
|
||||||
|
"core.endonesteptour": "tool_usertours",
|
||||||
"core.error": "moodle",
|
"core.error": "moodle",
|
||||||
"core.errorchangecompletion": "local_moodlemobileapp",
|
"core.errorchangecompletion": "local_moodlemobileapp",
|
||||||
"core.errordeletefile": "local_moodlemobileapp",
|
"core.errordeletefile": "local_moodlemobileapp",
|
||||||
|
@ -2349,7 +2351,6 @@
|
||||||
"core.usernotfullysetup": "error",
|
"core.usernotfullysetup": "error",
|
||||||
"core.users": "moodle",
|
"core.users": "moodle",
|
||||||
"core.usersuspended": "tool_reportbuilder",
|
"core.usersuspended": "tool_reportbuilder",
|
||||||
"core.endonesteptour": "tool_usertours",
|
|
||||||
"core.view": "moodle",
|
"core.view": "moodle",
|
||||||
"core.viewcode": "local_moodlemobileapp",
|
"core.viewcode": "local_moodlemobileapp",
|
||||||
"core.vieweditor": "local_moodlemobileapp",
|
"core.vieweditor": "local_moodlemobileapp",
|
||||||
|
|
|
@ -218,6 +218,12 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
||||||
|
if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) {
|
||||||
|
// There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it.
|
||||||
|
this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0);
|
||||||
|
this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
||||||
|
|
||||||
this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, {
|
this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, {
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-item class="ion-text-wrap core-group-selector" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
|
<ion-item class="ion-text-wrap core-group-selector" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
|
||||||
<ion-label id="addon-data-groupslabel">
|
<ion-label id="addon-data-groupslabel">
|
||||||
<ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsvisible' | translate }}</ng-container>
|
<ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ng-container>
|
||||||
<ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsseparate' | translate }}</ng-container>
|
<ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ng-container>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel"
|
<ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel"
|
||||||
interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}">
|
interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||||
|
|
|
@ -163,8 +163,10 @@ export class AddonModDataEditPage implements OnInit {
|
||||||
const entry = await AddonModDataHelper.fetchEntry(this.database, this.fieldsArray, this.entryId || 0);
|
const entry = await AddonModDataHelper.fetchEntry(this.database, this.fieldsArray, this.entryId || 0);
|
||||||
this.entry = entry.entry;
|
this.entry = entry.entry;
|
||||||
|
|
||||||
|
if (this.entryId) {
|
||||||
// Load correct group.
|
// Load correct group.
|
||||||
this.selectedGroup = this.entry.groupid;
|
this.selectedGroup = this.entry.groupid;
|
||||||
|
}
|
||||||
|
|
||||||
// Check permissions when adding a new entry or offline entry.
|
// Check permissions when adding a new entry or offline entry.
|
||||||
if (!this.isEditing) {
|
if (!this.isEditing) {
|
||||||
|
@ -172,6 +174,12 @@ export class AddonModDataEditPage implements OnInit {
|
||||||
|
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
||||||
|
if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) {
|
||||||
|
// There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it.
|
||||||
|
this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0);
|
||||||
|
this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
||||||
this.initialSelectedGroup = this.selectedGroup;
|
this.initialSelectedGroup = this.selectedGroup;
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,12 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
|
||||||
this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, { cmId: this.moduleId });
|
this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, { cmId: this.moduleId });
|
||||||
|
|
||||||
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule);
|
||||||
|
if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) {
|
||||||
|
// There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it.
|
||||||
|
this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0);
|
||||||
|
this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
|
||||||
|
|
||||||
const actions = AddonModDataHelper.getActions(this.database, this.access, this.entry!);
|
const actions = AddonModDataHelper.getActions(this.database, this.access, this.entry!);
|
||||||
|
|
|
@ -24,15 +24,18 @@
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-badge color="light" slot="end">{{ totalSize | coreBytesToSize }}
|
<ion-badge color="light" slot="end">
|
||||||
|
<ng-container *ngIf="!calculatingSize">{{ totalSize | coreBytesToSize }}</ng-container>
|
||||||
|
<ng-container *ngIf="calculatingSize">{{ 'core.calculating' | translate }}</ng-container>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin">
|
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin"
|
||||||
|
[disabled]="prefetchCourseData.loading">
|
||||||
<ion-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon>
|
<ion-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon>
|
||||||
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
|
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
|
||||||
{{ prefetchCourseData.statusTranslatable | translate }}
|
{{ prefetchCourseData.statusTranslatable | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="totalSize > 0" (click)="deleteForCourse()" expand="block" color="danger"
|
<ion-button [disabled]="calculatingSize || totalSize <= 0" (click)="deleteForCourse()" expand="block" color="danger"
|
||||||
class="ion-no-margin ion-margin-top">
|
class="ion-no-margin ion-margin-top">
|
||||||
<ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate:
|
<ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate:
|
||||||
{ name: title }">
|
{ name: title }">
|
||||||
|
@ -44,7 +47,13 @@
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ion-card class="section" *ngIf="section.modules.length > 0">
|
<ion-card class="section" *ngIf="section.modules.length > 0">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-item class="ion-no-padding" lines="full">
|
<ion-item class="ion-no-padding" [lines]="section.expanded ? 'full' : 'none'" button detail="false"
|
||||||
|
(click)="toggleExpand($event, section)" [class.core-course-storage-section-expanded]="section.expanded"
|
||||||
|
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||||
|
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-storage-section-' + section.id">
|
||||||
|
<ion-icon name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon"
|
||||||
|
[class.expandable-status-icon-expanded]="section.expanded">
|
||||||
|
</ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">
|
<p class="item-heading ion-text-wrap">
|
||||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="section.course"
|
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="section.course"
|
||||||
|
@ -52,18 +61,22 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</p>
|
</p>
|
||||||
<ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'"
|
<ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'"
|
||||||
*ngIf="section.totalSize > 0">
|
*ngIf="!section.calculatingSize && section.totalSize > 0">
|
||||||
<ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded"
|
<ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded"
|
||||||
[attr.aria-label]="'core.downloaded' | translate">
|
[attr.aria-label]="'core.downloaded' | translate">
|
||||||
</ion-icon>{{ section.totalSize | coreBytesToSize }}
|
</ion-icon>{{ section.totalSize | coreBytesToSize }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
|
<ion-badge color="light" *ngIf="section.calculatingSize">
|
||||||
|
{{ 'core.calculating' | translate }}
|
||||||
|
</ion-badge>
|
||||||
<!-- Download progress. -->
|
<!-- Download progress. -->
|
||||||
<p *ngIf="downloadEnabled && section.isDownloading">
|
<p *ngIf="downloadEnabled && section.isDownloading">
|
||||||
<core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total">
|
<core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total">
|
||||||
</core-progress-bar>
|
</core-progress-bar>
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<div class="storage-buttons" slot="end" *ngIf="section.totalSize > 0 || downloadEnabled">
|
<div class="storage-buttons" slot="end"
|
||||||
|
*ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
|
||||||
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
|
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
|
||||||
<core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded"
|
<core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded"
|
||||||
[status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)"
|
[status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)"
|
||||||
|
@ -77,7 +90,8 @@
|
||||||
{{section.count}} / {{section.total}}
|
{{section.count}} / {{section.total}}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</div>
|
</div>
|
||||||
<ion-button (click)="deleteForSection(section)" *ngIf="section.totalSize > 0" color="danger" fill="clear">
|
<ion-button (click)="deleteForSection(section)" *ngIf="!section.calculatingSize && section.totalSize > 0"
|
||||||
|
color="danger" fill="clear">
|
||||||
<ion-icon name="fas-trash" slot="icon-only"
|
<ion-icon name="fas-trash" slot="icon-only"
|
||||||
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
|
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
|
@ -85,9 +99,11 @@
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content>
|
<ion-card-content id="core-course-storage-section-{{section.id}}">
|
||||||
|
<ng-container *ngIf="section.expanded">
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<ion-item class="ion-no-padding core-course-storage-activity" *ngIf="downloadEnabled || module.totalSize > 0">
|
<ion-item class="ion-no-padding core-course-storage-activity"
|
||||||
|
*ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
|
||||||
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
||||||
[modname]="module.modname" [componentId]="module.instance">
|
[modname]="module.modname" [componentId]="module.instance">
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
|
@ -98,11 +114,14 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h3>
|
</h3>
|
||||||
<ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'"
|
<ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'"
|
||||||
*ngIf="module.totalSize > 0">
|
*ngIf="!module.calculatingSize && module.totalSize > 0">
|
||||||
<ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded"
|
<ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded"
|
||||||
[attr.aria-label]="'core.downloaded' | translate">
|
[attr.aria-label]="'core.downloaded' | translate">
|
||||||
</ion-icon>{{ module.totalSize | coreBytesToSize }}
|
</ion-icon>{{ module.totalSize | coreBytesToSize }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
|
<ion-badge color="light" *ngIf="module.calculatingSize">
|
||||||
|
{{ 'core.calculating' | translate }}
|
||||||
|
</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
||||||
<div class="storage-buttons" slot="end">
|
<div class="storage-buttons" slot="end">
|
||||||
|
@ -111,8 +130,8 @@
|
||||||
[canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner"
|
[canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner"
|
||||||
(action)="prefetchModule(module, section)">
|
(action)="prefetchModule(module, section)">
|
||||||
</core-download-refresh>
|
</core-download-refresh>
|
||||||
<ion-button fill="clear" (click)="deleteForModule(module, section)" *ngIf="module.totalSize > 0"
|
<ion-button fill="clear" (click)="deleteForModule(module, section)"
|
||||||
color="danger">
|
*ngIf="!module.calculatingSize && module.totalSize > 0" color="danger">
|
||||||
<ion-icon name="fas-trash" slot="icon-only"
|
<ion-icon name="fas-trash" slot="icon-only"
|
||||||
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
|
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
|
@ -120,6 +139,7 @@
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
CoreCourseHelper,
|
||||||
|
@ -30,6 +30,7 @@ import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +49,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
loaded = false;
|
loaded = false;
|
||||||
sections: AddonStorageManagerCourseSection[] = [];
|
sections: AddonStorageManagerCourseSection[] = [];
|
||||||
totalSize = 0;
|
totalSize = 0;
|
||||||
|
calculatingSize = true;
|
||||||
|
|
||||||
downloadEnabled = false;
|
downloadEnabled = false;
|
||||||
downloadCourseEnabled = false;
|
downloadCourseEnabled = false;
|
||||||
|
@ -61,6 +63,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
statusDownloaded = CoreConstants.DOWNLOADED;
|
statusDownloaded = CoreConstants.DOWNLOADED;
|
||||||
|
|
||||||
|
protected initialSectionId?: number;
|
||||||
protected siteUpdatedObserver?: CoreEventObserver;
|
protected siteUpdatedObserver?: CoreEventObserver;
|
||||||
protected courseStatusObserver?: CoreEventObserver;
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
protected sectionStatusObserver?: CoreEventObserver;
|
protected sectionStatusObserver?: CoreEventObserver;
|
||||||
|
@ -68,7 +71,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
protected isGuest = false;
|
protected isGuest = false;
|
||||||
|
|
||||||
constructor() {
|
constructor(protected elementRef: ElementRef) {
|
||||||
// Refresh the enabled flags if site is updated.
|
// Refresh the enabled flags if site is updated.
|
||||||
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
@ -99,21 +102,38 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
||||||
|
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||||
|
|
||||||
const sections = await CoreCourse.getSections(this.courseId, false, true);
|
const sections = (await CoreCourse.getSections(this.courseId, false, true))
|
||||||
|
.filter((section) => !CoreCourseHelper.isSectionStealth(section));
|
||||||
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
||||||
.map((section) => ({ ...section, totalSize: 0 }));
|
.map(section => ({
|
||||||
|
...section,
|
||||||
|
totalSize: 0,
|
||||||
|
calculatingSize: true,
|
||||||
|
expanded: section.id === this.initialSectionId,
|
||||||
|
modules: section.modules.map(module => ({
|
||||||
|
...module,
|
||||||
|
calculatingSize: true,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
|
||||||
|
CoreDom.scrollToElement(
|
||||||
|
this.elementRef.nativeElement,
|
||||||
|
'.core-course-storage-section-expanded',
|
||||||
|
{ addYAxis: -10 },
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadSizes(),
|
this.initSizes(),
|
||||||
this.initCoursePrefetch(),
|
this.initCoursePrefetch(),
|
||||||
this.initModulePrefetch(),
|
this.initModulePrefetch(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,15 +259,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Init section, course and modules sizes.
|
* Init section, course and modules sizes.
|
||||||
*/
|
*/
|
||||||
protected async loadSizes(): Promise<void> {
|
protected async initSizes(): Promise<void> {
|
||||||
this.totalSize = 0;
|
await Promise.all(this.sections.map(async (section) => {
|
||||||
|
await Promise.all(section.modules.map(async (module) => {
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
this.sections.forEach((section) => {
|
|
||||||
section.totalSize = 0;
|
|
||||||
section.modules.forEach((module) => {
|
|
||||||
module.totalSize = 0;
|
|
||||||
|
|
||||||
// Note: This function only gets the size for modules which are downloadable.
|
// Note: This function only gets the size for modules which are downloadable.
|
||||||
// For other modules it always returns 0, even if they have downloaded some files.
|
// For other modules it always returns 0, even if they have downloaded some files.
|
||||||
// However there is no 100% reliable way to actually track the files in this case.
|
// However there is no 100% reliable way to actually track the files in this case.
|
||||||
|
@ -255,7 +269,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
||||||
// There is nothing enforcing correct values.
|
// There is nothing enforcing correct values.
|
||||||
// Most modules which have large files are downloadable, so I think this is sufficient.
|
// Most modules which have large files are downloadable, so I think this is sufficient.
|
||||||
const promise = CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId).then((size) => {
|
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
|
||||||
|
|
||||||
// There are some cases where the return from this is not a valid number.
|
// There are some cases where the return from this is not a valid number.
|
||||||
if (!isNaN(size)) {
|
if (!isNaN(size)) {
|
||||||
module.totalSize = Number(size);
|
module.totalSize = Number(size);
|
||||||
|
@ -263,13 +278,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.totalSize += size;
|
this.totalSize += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
module.calculatingSize = false;
|
||||||
});
|
}));
|
||||||
promises.push(promise);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
section.calculatingSize = false;
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.calculatingSize = false;
|
||||||
|
|
||||||
// Mark course as not downloaded if course size is 0.
|
// Mark course as not downloaded if course size is 0.
|
||||||
if (this.totalSize == 0) {
|
if (this.totalSize == 0) {
|
||||||
|
@ -277,6 +292,56 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the sizes of some modules.
|
||||||
|
*
|
||||||
|
* @param modules Modules.
|
||||||
|
* @param section Section the modules belong to.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async updateModulesSizes(
|
||||||
|
modules: AddonStorageManagerModule[],
|
||||||
|
section?: AddonStorageManagerCourseSection,
|
||||||
|
): Promise<void> {
|
||||||
|
this.calculatingSize = true;
|
||||||
|
|
||||||
|
await Promise.all(modules.map(async (module) => {
|
||||||
|
if (module.calculatingSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.calculatingSize = true;
|
||||||
|
|
||||||
|
if (!section) {
|
||||||
|
section = this.sections.find((section) => section.modules.some((mod) => mod.id === module.id));
|
||||||
|
if (section) {
|
||||||
|
section.calculatingSize = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
|
||||||
|
|
||||||
|
const diff = (isNaN(size) ? 0 : size) - (module.totalSize ?? 0);
|
||||||
|
|
||||||
|
module.totalSize = Number(size);
|
||||||
|
this.totalSize += diff;
|
||||||
|
if (section) {
|
||||||
|
section.totalSize += diff;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore errors, it shouldn't happen.
|
||||||
|
} finally {
|
||||||
|
module.calculatingSize = false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.calculatingSize = false;
|
||||||
|
if (section) {
|
||||||
|
section.calculatingSize = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has requested a delete for the whole course data.
|
* The user has requested a delete for the whole course data.
|
||||||
*
|
*
|
||||||
|
@ -401,7 +466,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
await this.loadSizes();
|
await this.updateModulesSizes(modules, section);
|
||||||
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,7 +515,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await this.loadSizes();
|
await this.updateModulesSizes(section.modules, section);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// User cancelled or there was an error calculating the size.
|
// User cancelled or there was an error calculating the size.
|
||||||
|
@ -496,7 +561,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
module.spinner = false;
|
module.spinner = false;
|
||||||
|
|
||||||
await this.loadSizes();
|
await this.updateModulesSizes([module]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,6 +652,18 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle expand status.
|
||||||
|
*
|
||||||
|
* @param event Event object.
|
||||||
|
* @param section Section to expand / collapse.
|
||||||
|
*/
|
||||||
|
toggleExpand(event: Event, section: AddonStorageManagerCourseSection): void {
|
||||||
|
section.expanded = !section.expanded;
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -608,11 +685,14 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
||||||
totalSize: number;
|
totalSize: number;
|
||||||
|
calculatingSize: boolean;
|
||||||
|
expanded: boolean;
|
||||||
modules: AddonStorageManagerModule[];
|
modules: AddonStorageManagerModule[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddonStorageManagerModule = CoreCourseModuleData & {
|
type AddonStorageManagerModule = CoreCourseModuleData & {
|
||||||
totalSize?: number;
|
totalSize?: number;
|
||||||
|
calculatingSize: boolean;
|
||||||
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
||||||
spinner?: boolean;
|
spinner?: boolean;
|
||||||
downloadStatus?: string;
|
downloadStatus?: string;
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreContentLinksChooseSiteModalComponent } from '../components/choose-site-modal/choose-site-modal';
|
import { CoreContentLinksChooseSiteModalComponent } from '../components/choose-site-modal/choose-site-modal';
|
||||||
|
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding content links.
|
* Service that provides some features regarding content links.
|
||||||
|
@ -138,6 +139,12 @@ export class CoreContentLinksHelperProvider {
|
||||||
openBrowserRoot?: boolean,
|
openBrowserRoot?: boolean,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
if (CoreCustomURLSchemes.isCustomURL(url)) {
|
||||||
|
await CoreCustomURLSchemes.handleCustomURL(url);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (checkRoot) {
|
if (checkRoot) {
|
||||||
const data = await CoreSites.isStoredRootURL(url, username);
|
const data = await CoreSites.isStoredRootURL(url, username);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
<core-navbar-buttons slot="end" prepend>
|
||||||
|
<ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate">
|
||||||
|
<ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</core-navbar-buttons>
|
||||||
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
||||||
<!-- Default course format. -->
|
<!-- Default course format. -->
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
|
|
|
@ -392,15 +392,18 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the course index modal.
|
* Get selected section ID. If viewing all sections, use current scrolled section.
|
||||||
|
*
|
||||||
|
* @return Section ID, undefined if not found.
|
||||||
*/
|
*/
|
||||||
async openCourseIndex(): Promise<void> {
|
protected async getSelectedSectionId(): Promise<number | undefined> {
|
||||||
let selectedId = this.selectedSection?.id;
|
if (this.selectedSection?.id !== this.allSectionsId) {
|
||||||
|
return this.selectedSection?.id;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedId == this.allSectionsId) {
|
|
||||||
// Check current scrolled section.
|
// Check current scrolled section.
|
||||||
const allSectionElements: NodeListOf<HTMLElement> =
|
const allSectionElements: NodeListOf<HTMLElement> =
|
||||||
this.elementRef.nativeElement.querySelectorAll('section.section-wrapper');
|
this.elementRef.nativeElement.querySelectorAll('section.core-course-module-list-wrapper');
|
||||||
|
|
||||||
const scroll = await this.content.getScrollElement();
|
const scroll = await this.content.getScrollElement();
|
||||||
const containerTop = scroll.getBoundingClientRect().top;
|
const containerTop = scroll.getBoundingClientRect().top;
|
||||||
|
@ -412,9 +415,15 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
return position.bottom >= containerTop;
|
return position.bottom >= containerTop;
|
||||||
});
|
});
|
||||||
|
|
||||||
selectedId = Number(element?.getAttribute('id')) || undefined;
|
return Number(element?.getAttribute('id')) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the course index modal.
|
||||||
|
*/
|
||||||
|
async openCourseIndex(): Promise<void> {
|
||||||
|
const selectedId = await this.getSelectedSectionId();
|
||||||
|
|
||||||
const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({
|
const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({
|
||||||
component: CoreCourseCourseIndexComponent,
|
component: CoreCourseCourseIndexComponent,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
@ -453,6 +462,23 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.moduleId = data.moduleId;
|
this.moduleId = data.moduleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open course downloads page.
|
||||||
|
*/
|
||||||
|
async gotoCourseDownloads(): Promise<void> {
|
||||||
|
const selectedId = await this.getSelectedSectionId();
|
||||||
|
|
||||||
|
CoreNavigator.navigateToSitePath(
|
||||||
|
`storage/${this.course.id}`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
title: this.course.fullname,
|
||||||
|
sectionId: selectedId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when selected section changes.
|
* Function called when selected section changes.
|
||||||
*
|
*
|
||||||
|
|
|
@ -48,14 +48,15 @@
|
||||||
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
|
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
|
||||||
[attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon>
|
[attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<div id="core-course-index-section-{{section.id}}">
|
||||||
<ng-container *ngIf="section.expanded">
|
<ng-container *ngIf="section.expanded">
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted"
|
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted"
|
||||||
(click)="selectSectionOrModule($event, section.id, module.id)" button>
|
(click)="selectSectionOrModule($event, section.id, module.id)" button>
|
||||||
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
|
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
|
||||||
slot="start" aria-hidden="true"></ion-icon>
|
slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0"
|
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
|
||||||
slot="start" [attr.aria-label]="'core.course.todo' | translate">
|
*ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
<ion-icon class="completioninfo completion_complete" name="fas-circle"
|
<ion-icon class="completioninfo completion_complete" name="fas-circle"
|
||||||
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
|
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
<core-navbar-buttons slot="end" prepend>
|
|
||||||
<ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate">
|
|
||||||
<ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</core-navbar-buttons>
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
|
|
@ -366,14 +366,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gotoCourseDownloads(): void {
|
|
||||||
CoreNavigator.navigateToSitePath(
|
|
||||||
`storage/${this.course.id}`,
|
|
||||||
{ params: { title: this.course.fullname } },
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,10 +17,11 @@ import { CoreIframeUtils } from '@services/utils/iframe';
|
||||||
import { Platform } from '@singletons';
|
import { Platform } from '@singletons';
|
||||||
|
|
||||||
export default async function(): Promise<void> {
|
export default async function(): Promise<void> {
|
||||||
|
await Platform.ready();
|
||||||
|
|
||||||
if (!CoreApp.isIOS() || !('WKUserScript' in window)) {
|
if (!CoreApp.isIOS() || !('WKUserScript' in window)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Platform.ready();
|
|
||||||
CoreIframeUtils.injectiOSScripts(window);
|
CoreIframeUtils.injectiOSScripts(window);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"areyousure": "Are you sure?",
|
"areyousure": "Are you sure?",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"browser": "Browser",
|
"browser": "Browser",
|
||||||
|
"calculating": "Calculating",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"cannotconnect": "Cannot connect",
|
"cannotconnect": "Cannot connect",
|
||||||
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||||
|
|
|
@ -446,26 +446,13 @@ export class CoreIframeUtilsProvider {
|
||||||
} else {
|
} else {
|
||||||
element.setAttribute('src', url);
|
element.setAttribute('src', url);
|
||||||
}
|
}
|
||||||
} else if (CoreUrlUtils.isLocalFileUrl(url)) {
|
} else {
|
||||||
// It's a local file.
|
|
||||||
const filename = url.substring(url.lastIndexOf('/') + 1);
|
|
||||||
|
|
||||||
if (!CoreFileHelper.isOpenableInApp({ filename })) {
|
|
||||||
try {
|
try {
|
||||||
await CoreFileHelper.showConfirmOpenUnsupportedFile();
|
// It's an external link or a local file, check if it can be opened in the app.
|
||||||
} catch (error) {
|
await CoreWindow.open(url, name);
|
||||||
return; // Cancelled, stop.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await CoreUtils.openFile(url);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// It's an external link, check if it can be opened in the app.
|
|
||||||
await CoreWindow.open(url, name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue