Merge pull request #2216 from crazyserver/MOBILE-3213

Mobile 3213
main
Juan Leyva 2019-12-17 11:43:42 +01:00 committed by GitHub
commit dbb9e86e4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 162 additions and 73 deletions

View File

@ -2,6 +2,10 @@ ion-app.app-root addon-mod-forum-post .addon-mod_forum-post {
background-color: $white;
border-bottom: 1px solid $list-md-border-color;
@include darkmode() {
background-color: $core-dark-item-bg-color;
}
.addon-forum-star {
color: $core-star-color;
}

View File

@ -2,7 +2,7 @@ ion-app.app-root page-addon-mod-forum-discussion {
.highlight .card-header .item {
background-color: $gray-lighter;
@include darkmode() {
background-color: $black;
background-color: $gray-dark;
}
}

View File

@ -22,6 +22,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreRatingProvider, CoreRatingInfo } from '@core/rating/providers/rating';
import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
@ -57,7 +58,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
isOnline: boolean;
isSplitViewOn: boolean;
postHasOffline: boolean;
sort: SortType = 'flat-oldest';
sort: SortType = 'nested';
trackPosts: boolean;
replyData = {
replyingTo: 0,
@ -96,19 +97,20 @@ export class AddonModForumDiscussionPage implements OnDestroy {
constructor(navParams: NavParams,
network: Network,
zone: NgZone,
private appProvider: CoreAppProvider,
private eventsProvider: CoreEventsProvider,
private sitesProvider: CoreSitesProvider,
private domUtils: CoreDomUtilsProvider,
private utils: CoreUtilsProvider,
private translate: TranslateService,
private uploaderProvider: CoreFileUploaderProvider,
private forumProvider: AddonModForumProvider,
private forumOffline: AddonModForumOfflineProvider,
private forumHelper: AddonModForumHelperProvider,
private forumSync: AddonModForumSyncProvider,
private ratingOffline: CoreRatingOfflineProvider,
@Optional() private svComponent: CoreSplitViewComponent,
protected appProvider: CoreAppProvider,
protected eventsProvider: CoreEventsProvider,
protected sitesProvider: CoreSitesProvider,
protected domUtils: CoreDomUtilsProvider,
protected utils: CoreUtilsProvider,
protected translate: TranslateService,
protected uploaderProvider: CoreFileUploaderProvider,
protected forumProvider: AddonModForumProvider,
protected forumOffline: AddonModForumOfflineProvider,
protected forumHelper: AddonModForumHelperProvider,
protected forumSync: AddonModForumSyncProvider,
protected ratingOffline: CoreRatingOfflineProvider,
protected userProvider: CoreUserProvider,
@Optional() protected svComponent: CoreSplitViewComponent,
protected navCtrl: NavController) {
this.courseId = navParams.get('courseId');
this.cmId = navParams.get('cmId');
@ -134,7 +136,29 @@ export class AddonModForumDiscussionPage implements OnDestroy {
* View loaded.
*/
ionViewDidLoad(): void {
this.sitesProvider.getCurrentSite().getLocalSiteConfig('AddonModForumDiscussionSort', this.sort).then((value) => {
this.sitesProvider.getCurrentSite().getLocalSiteConfig('AddonModForumDiscussionSort').catch(() => {
this.userProvider.getUserPreference('forum_displaymode').catch(() => {
// Ignore errors.
}).then((value) => {
const sortValue = value && parseInt(value, 10);
switch (sortValue) {
case 1:
this.sort = 'flat-oldest';
break;
case -1:
this.sort = 'flat-newest';
break;
case 3:
this.sort = 'nested';
break;
case 2: // Threaded not implemented.
default:
// Not set, use default sort.
// @TODO add fallback to $CFG->forum_displaymode.
}
});
}).then((value) => {
this.sort = value;
}).finally(() => {
this.fetchPosts(true, false, true).then(() => {

View File

@ -51,8 +51,8 @@
<h3>{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.number}}</h3>
<p item-content *ngIf="attempt.grade != -1">{{ attempt.grade }}</p>
<p item-content *ngIf="attempt.grade == -1">{{ 'addon.mod_scorm.cannotcalculategrade' | translate }}</p>
<p item-content *ngIf="scorm.maxattempt == 0 || attempt.number <= scorm.maxattempt">{{ 'addon.mod_scorm.offlineattemptnote' | translate }}</p>
<p item-content *ngIf="scorm.maxattempt != 0 && attempt.number > scorm.maxattempt">{{ 'addon.mod_scorm.offlineattemptovermax' | translate }}</p>
<p *ngIf="scorm.maxattempt == 0 || attempt.number <= scorm.maxattempt">{{ 'addon.mod_scorm.offlineattemptnote' | translate }}</p>
<p *ngIf="scorm.maxattempt != 0 && attempt.number > scorm.maxattempt">{{ 'addon.mod_scorm.offlineattemptovermax' | translate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="scorm.displayattemptstatus && scorm.gradeMethodReadable">
<h3>{{ 'addon.mod_scorm.grademethod' | translate }}</h3>
@ -95,9 +95,9 @@
<!-- If data shown doesn't belong to last attempt, show a warning. -->
<p *ngIf="attemptToContinue">{{ 'addon.mod_scorm.dataattemptshown' | translate:{number: attemptToContinue} }}</p>
<p>{{ currentOrganization.title }}</p>
<div *ngFor="let sco of toc" class="core-padding-{{sco.level}}">
<div *ngFor="let sco of toc" class="core-padding-{{sco.level}} addon-mod_scorm-type-{{sco.scormtype}}">
<p *ngIf="sco.isvisible">
<img [src]="sco.image.url" [alt]="sco.image.description" />
<core-icon [name]="sco.image.icon" [label]="sco.image.description" item-start></core-icon>
<a *ngIf="sco.prereq && sco.launch" (click)="open($event, sco.id)"><core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></a>
<span *ngIf="!sco.prereq || !sco.launch"><core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></span>
<span *ngIf="accessInfo && accessInfo.canviewscores && sco.score_raw">({{ 'addon.mod_scorm.score' | translate }}: {{sco.score_raw}})</span>

View File

@ -1,9 +1,13 @@
ion-app.app-root addon-mod-scorm-index {
ion-app.app-root addon-mod-scorm-index,
ion-app.app-root page-addon-mod-scorm-toc {
.addon-mod_scorm-toc {
img {
width: auto;
display: inline;
// Hide all non sco icons using css to maintain padding.
ion-icon {
opacity: 0;
}
.addon-mod_scorm-type-sco ion-icon {
opacity: 1
}
}
}

View File

@ -10,7 +10,7 @@
</ion-header>
<ion-content>
<nav>
<ion-list>
<ion-list class="addon-mod_scorm-toc">
<ion-item text-wrap *ngIf="attemptToContinue">
<p>{{ 'addon.mod_scorm.dataattemptshown' | translate:{number: attemptToContinue} }}</p>
</ion-item>
@ -23,8 +23,8 @@
<!-- List of SCOs. -->
<ng-container *ngFor="let sco of toc">
<a *ngIf="sco.isvisible" ion-item text-wrap [ngClass]="'core-padding-' + sco.level" [class.core-nav-item-selected]="selected == sco.id" (click)="loadSco(sco)" [attr.disabled]="!sco.prereq || !sco.launch ? true : null" [attr.detail-none]="!sco.prereq || !sco.launch ? true : null">
<img [src]="sco.image.url" [alt]="sco.image.description" />
<a *ngIf="sco.isvisible" ion-item text-wrap [ngClass]="'core-padding-' + sco.level + ' addon-mod_scorm-type-' + sco.scormtype" [class.core-nav-item-selected]="selected == sco.id" [class]="" (click)="loadSco(sco)" [attr.disabled]="!sco.prereq || !sco.launch ? true : null" [attr.detail-none]="!sco.prereq || !sco.launch ? true : null">
<core-icon [name]="sco.image.icon" [label]="sco.image.description" item-start></core-icon>
<core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId"></core-format-text>
<span *ngIf="accessInfo && accessInfo.canviewscores && sco.score_raw">({{ 'addon.mod_scorm.score' | translate }}: {{sco.score_raw}})</span>
</a>

View File

@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreComponentsModule } from '@components/components.module';
import { AddonModScormTocPage } from './toc';
@NgModule({
@ -24,6 +25,7 @@ import { AddonModScormTocPage } from './toc';
],
imports: [
CoreDirectivesModule,
CoreComponentsModule,
IonicPageModule.forChild(AddonModScormTocPage),
TranslateModule.forChild()
],

View File

@ -106,6 +106,22 @@ export class AddonModScormProvider {
'b': 'browsed',
'n': 'notattempted'
};
protected static STATUS_TO_ICON = {
assetc: 'fa-file-archive-o',
asset: 'fa-file-archive-o',
browsed: 'fa-book',
completed: 'fa-check-square-o',
failed: 'fa-times',
incomplete: 'fa-pencil-square-o',
minus: 'fa-minus',
notattempted: 'fa-square-o',
passed: 'fa-check',
plus: 'fa-plus',
popdown: 'fa-window-close-o',
popup: 'fa-window-restore',
suspend: 'fa-pause',
wait: 'fa-clock-o',
};
protected ROOT_CACHE_KEY = 'mmaModScorm:';
protected logger;
@ -1048,39 +1064,56 @@ export class AddonModScormProvider {
* @param incomplete Whether the SCORM is incomplete.
* @return Image URL and description.
*/
getScoStatusIcon(sco: any, incomplete?: boolean): {url: string, description: string} {
getScoStatusIcon(sco: any, incomplete?: boolean): {icon: string, description: string} {
let imageName = '',
descName = '',
status;
suspendedStr = '';
if (sco.scormtype == 'sco') {
// Not an asset, calculate image using status.
status = sco.status;
if (this.VALID_STATUSES.indexOf(status) < 0) {
// Status empty or not valid, use 'notattempted'.
status = 'notattempted';
}
const status = sco.status;
if (!incomplete) {
// Check if SCO is completed or not. If SCORM is incomplete there's no need to check SCO.
incomplete = this.isStatusIncomplete(status);
}
if (sco.isvisible) {
if (this.VALID_STATUSES.indexOf(status) >= 0) {
if (sco.scormtype == 'sco') {
imageName = status;
descName = status;
} else {
imageName = 'asset';
descName = 'assetlaunched';
}
if (incomplete && sco.exitvalue == 'suspend') {
imageName = 'suspend';
descName = 'suspended';
if (!incomplete) {
// Check if SCO is completed or not. If SCORM is incomplete there's no need to check SCO.
incomplete = this.isStatusIncomplete(status);
}
if (incomplete && sco.exitvalue == 'suspend') {
imageName = 'suspend';
suspendedStr = ' - ' + this.translate.instant('addon.mod_scorm.suspended');
}
} else {
imageName = sco.status;
descName = sco.status;
incomplete = true;
if (sco.scormtype == 'sco') {
// Status empty or not valid, use 'notattempted'.
imageName = 'notattempted';
} else {
imageName = 'asset';
}
descName = imageName;
}
} else {
imageName = 'asset';
descName = (!sco.status || sco.status == 'notattempted') ? 'asset' : 'assetlaunched';
}
if (imageName == '') {
imageName = 'notattempted';
descName = 'notattempted';
suspendedStr = '';
}
sco.incomplete = incomplete;
return {
url: 'assets/img/scorm/' + imageName + '.gif',
description: this.translate.instant('addon.mod_scorm.' + descName)
icon: AddonModScormProvider.STATUS_TO_ICON[imageName],
description: this.translate.instant('addon.mod_scorm.' + descName) + suspendedStr
};
}

View File

@ -39,8 +39,8 @@
<div *ngIf="question.multiArray && question.multiArray.length" [attr.padding-top]="index == 1">
<h3 padding-horizontal>{{ question.text }}</h3>
<ion-grid no-padding>
<ion-row no-padding align-items-center class="hidden-phone">
<ion-col col-7>
<ion-row no-padding nowrap align-items-center class="hidden-phone">
<ion-col col-6>
<div padding>{{ 'addon.mod_survey.responses' | translate }}</div>
</ion-col>
<ion-col text-center *ngFor="let option of question.optionsArray">
@ -56,7 +56,7 @@
<!-- Subquestion -->
<ion-grid no-padding *ngIf="question.parent !== 0" text-wrap [class.even]="isEven">
<ion-row no-padding nowrap align-items-center radio-group [(ngModel)]="answers[question.name]" [required]="question.required">
<ion-col col-7>
<ion-col col-6>
<ion-label padding-horizontal [core-mark-required]="question.required" id="addon-mod_survey-{{question.name}}"><strong>{{question.num}}.</strong> {{ question.text }}</ion-label>
</ion-col>
@ -77,13 +77,13 @@
<ng-container *ngIf="(!question.multiArray || question.multiArray.length == 0) && question.parent === 0">
<ion-grid no-padding text-wrap *ngIf="question.type > 0" [class.even]="isEven">
<ion-row no-padding align-items-center>
<ion-col col-7>
<ion-col col-6>
<ion-label [core-mark-required]="question.required" padding-horizontal id="addon-mod_survey-{{question.name}}"><strong>{{question.num}}.</strong> {{ question.text }}</ion-label>
</ion-col>
<ion-col col-5>
<ion-col col-6>
<ion-select padding [(ngModel)]="answers[question.name]" [attr.aria-labelledby]="'addon-mod_survey-'+question.name" interface="action-sheet" [required]="question.required">
<ion-option *ngFor="let option of question.optionsArray; let value=index;" [value]="value">{{option}}</ion-option>
</ion-select>
<ion-option *ngFor="let option of question.optionsArray; let value=index;" [value]="value">{{option}}</ion-option>
</ion-select>
</ion-col>
</ion-row>
</ion-grid>

View File

@ -163,13 +163,10 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
* @return If answers are valid
*/
isValidResponse(): boolean {
for (const x in this.answers) {
if (this.answers[x] === -1) {
return false;
}
}
return true;
return !this.questions.some((question) => {
return question.required && question.name &&
(question.type === 0 ? this.answers[question.name] == '' : parseInt(this.answers[question.name], 10) === -1);
});
}
/**

View File

@ -4,6 +4,11 @@ $addon-mod-wiki-toc-title-color: $gray-darker !default;
$addon-mod-wiki-toc-border-color: $gray-dark !default;
$addon-mod-wiki-toc-background-color: $gray-light !default;
$addon-mod-wiki-dark-toc-title-color: $white !default;
$addon-mod-wiki-dark-toc-border-color: $gray-dark !default;
$addon-mod-wiki-dark-toc-background-color: $gray-darker !default;
$addon-mod-wiki-dark-newentry-link-color: $red-light !default;
ion-app.app-root addon-mod-wiki-index {
background-color: $white;
@include darkmode() {
@ -28,6 +33,17 @@ ion-app.app-root addon-mod-wiki-index {
a {
color: $link-color;
}
@include darkmode() {
border: 1px solid $addon-mod-wiki-dark-toc-border-color;
background: $addon-mod-wiki-dark-toc-background-color;
p {
color: $core-dark-text-color !important;
}
a {
color: $core-dark-link-color;
}
}
margin: 16px;
padding: 8px;
}
@ -55,6 +71,9 @@ ion-app.app-root addon-mod-wiki-index {
.wiki_newentry {
color: $addon-mod-wiki-newentry-link-color;
font-style: italic;
@include darkmode() {
color: $addon-mod-wiki-dark-newentry-link-color !important;
}
}
/* Hide edit section links */

View File

@ -500,7 +500,7 @@ ion-app.app-root {
@include darkmode() {
ion-select.core-button-select,
.core-button-select {
background-color: $core-dark-item-divider-bg-color;
background-color: $core-dark-item-bg-color;
&.select-md,
@ -509,7 +509,7 @@ ion-app.app-root {
&.button-ios,
&.select-wp,
&.button-wp {
background: $core-dark-item-divider-bg-color;
background: $core-dark-item-bg-color;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

View File

@ -82,6 +82,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy {
!this.ariaLabel && this.newElement.setAttribute('aria-hidden', 'true');
!this.ariaLabel && this.newElement.setAttribute('role', 'presentation');
this.ariaLabel && this.newElement.setAttribute('aria-label', this.ariaLabel);
this.ariaLabel && this.newElement.setAttribute('title', this.ariaLabel);
const attrs = this.element.attributes;
for (let i = attrs.length - 1; i >= 0; i--) {

View File

@ -11,7 +11,7 @@
<core-loading [hideUntil]="loaded">
<!-- Section selector. -->
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal" [class.core-section-download]="downloadEnabled">
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal core-button-selector-row" [class.core-section-download]="downloadEnabled">
<button float-start ion-button icon-start icon-end (click)="showSectionSelector($event)" color="light" class="core-button-select button-no-uppercase" ion-col [attr.aria-label]="('core.course.sections' | translate) + ': ' + (selectedSection && (selectedSection.formattedName || selectedSection.name))" aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded" aria-controls="core-course-section-selector" id="core-course-section-button">
<core-icon name="fa-folder"></core-icon>
<span class="core-button-select-text">{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}</span>

View File

@ -64,6 +64,10 @@ ion-app.app-root core-course-format {
@include padding(null, 0, null, null);
}
.core-button-selector-row {
@include safe-area-padding-start($content-padding !important, $content-padding);
}
.core-course-section-nav-buttons {
.button-inner core-format-text {
white-space: nowrap;

View File

@ -88,7 +88,7 @@ export class ZipMock extends Zip {
const destParent = destination.substring(0, source.lastIndexOf('/')),
destFolderName = destination.substr(source.lastIndexOf('/') + 1);
return this.file.createDir(destParent, destFolderName, false);
return this.file.createDir(destParent, destFolderName, true);
}).then(() => {
const promises = [],

View File

@ -1017,7 +1017,7 @@ export class CoreFileProvider {
return this.zip.unzip(fileEntry.toURL(), destFolder, onProgress);
}).then((result) => {
if (result == -1) {
return Promise.reject(null);
return Promise.reject('Unzip failed.');
}
});
}

View File

@ -3,8 +3,8 @@ $core-dark-text-secondary-color: $gray-light !default;
$core-dark-link-color: $blue-light !default;
$core-dark-item-bg-color: $gray-darker !default;
$core-dark-item-divider-bg-color: $black !default;
$core-dark-background-color: $gray-dark !default;
$core-dark-item-divider-bg-color: $gray-dark !default;
$core-dark-background-color: $black !default;
// Login.
$core-dark-login-page-background-color: radial-gradient(white, $gray-dark) !default;
@ -31,7 +31,8 @@ ion-app.app-root .ion-page {
.item,
.item-select,
ion-card,
.card-header {
.card-header,
.card-content {
color: $core-dark-text-color;
background-color: $core-dark-item-bg-color;