Merge pull request #3108 from crazyserver/MOBILE-3814

Mobile 3814
main
Dani Palou 2022-02-11 09:10:22 +01:00 committed by GitHub
commit 7736d899a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 935 additions and 534 deletions

View File

@ -73,7 +73,8 @@
<!-- List of courses. --> <!-- List of courses. -->
<div class="safe-area-padding" *ngIf="hasCourses"> <div class="safe-area-padding" *ngIf="hasCourses">
<ion-grid class="ion-no-padding" [class.core-no-grid]="layouts.selected != 'card'"> <ion-grid class="ion-no-padding" [class.core-no-grid]="layouts.selected != 'card'"
[class.list-item-limited-width]="layouts.selected != 'card'">
<ion-row class="ion-no-padding"> <ion-row class="ion-no-padding">
<ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" size-lg="4" <ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" size-lg="4"
size-xl="3"> size-xl="3">

View File

@ -4,7 +4,7 @@
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false">
<ng-container *ngIf="mainMenuBlock"> <ion-list *ngIf="mainMenuBlock" class="core-course-module-list-wrapper">
<ion-item class="ion-text-wrap" *ngIf="mainMenuBlock.summary"> <ion-item class="ion-text-wrap" *ngIf="mainMenuBlock.summary">
<ion-label> <ion-label>
<core-format-text [text]="mainMenuBlock.summary" [component]="component" [componentId]="siteHomeId" contextLevel="course" <core-format-text [text]="mainMenuBlock.summary" [component]="component" [componentId]="siteHomeId" contextLevel="course"
@ -13,5 +13,5 @@
</ion-item> </ion-item>
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [section]="mainMenuBlock"></core-course-module> <core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [section]="mainMenuBlock"></core-course-module>
</ng-container> </ion-list>
</core-loading> </core-loading>

View File

@ -1,13 +1,14 @@
<ion-item lines="none" *ngIf="course"> <ion-item *ngIf="course">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<h3> <h3>
<span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span> <span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text> <core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id">
</core-format-text>
</h3> </h3>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-group *ngFor="let dayEvents of filteredEvents"> <ion-item-group *ngFor="let dayEvents of filteredEvents">
<ion-item lines="none"> <ion-item>
<ion-label> <ion-label>
<h4 [class.core-bold]="!course">{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedaydate" }}</h4> <h4 [class.core-bold]="!course">{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedaydate" }}</h4>
</ion-label> </ion-label>
@ -20,7 +21,7 @@
<ion-col class="addon-block-timeline-activity-main ion-no-padding"> <ion-col class="addon-block-timeline-activity-main ion-no-padding">
<ion-row class="ion-justify-content-between ion-align-items-center ion-nowrap ion-no-padding"> <ion-row class="ion-justify-content-between ion-align-items-center ion-nowrap ion-no-padding">
<ion-col class="addon-block-timeline-activity-time ion-no-padding"> <ion-col class="addon-block-timeline-activity-time ion-no-padding">
<ion-badge color="light">{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</ion-badge> <small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small>
<core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance" <core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance"
[modname]="event.modulename"> [modname]="event.modulename">
</core-mod-icon> </core-mod-icon>
@ -52,7 +53,7 @@
</ion-row> </ion-row>
</ion-col> </ion-col>
<ion-col class="addon-block-timeline-activity-action ion-no-padding" *ngIf="event.action?.actionable"> <ion-col class="addon-block-timeline-activity-action ion-no-padding" *ngIf="event.action?.actionable">
<ion-button fill="clear" (click)="action($event, event.action.url)" [title]="event.action.name"> <ion-button fill="outline" color="medium" (click)="action($event, event.action.url)" [title]="event.action.name">
{{event.action.name}} {{event.action.name}}
<ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount"> <ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount">
{{event.action.itemcount}} {{event.action.itemcount}}
@ -73,7 +74,7 @@
<ion-spinner *ngIf="loadingMore" [attr.aria-label]="'core.loading' | translate"></ion-spinner> <ion-spinner *ngIf="loadingMore" [attr.aria-label]="'core.loading' | translate"></ion-spinner>
</div> </div>
<ion-item lines="none" *ngIf="empty && course"> <ion-item *ngIf="empty && course">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<p>{{'addon.block_timeline.noevents' | translate}}</p> <p>{{'addon.block_timeline.noevents' | translate}}</p>
</ion-label> </ion-label>

View File

@ -13,13 +13,20 @@ h4.core-bold {
font-weight: bold; font-weight: bold;
} }
.addon-block-timeline-activity ion-badge { .addon-block-timeline-activity {
@include margin-horizontal(0.25rem, 0.5rem); ion-badge {
} @include margin-horizontal(0.25rem, 0.5rem);
}
.addon-block-timeline-activity core-mod-icon { small {
--margin-end: 0.5rem; @include margin-horizontal(null, 0.5rem);
--margin-vertical: 0; }
core-mod-icon {
padding: 8px;
--margin-end: 0.5rem;
--margin-vertical: 0;
}
} }
.addon-block-timeline-activity-time { .addon-block-timeline-activity-time {

View File

@ -27,7 +27,7 @@
</ion-item> </ion-item>
<!-- Custom value. --> <!-- Custom value. -->
<ion-item lines="none" class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p>{{ 'core.custom' | translate }}</p> <p>{{ 'core.custom' | translate }}</p>
</ion-label> </ion-label>

View File

@ -147,7 +147,7 @@
</ion-label> </ion-label>
<ion-radio slot="end" [value]="0"></ion-radio> <ion-radio slot="end" [value]="0"></ion-radio>
</ion-item> </ion-item>
<ion-item lines="none"> <ion-item>
<ion-label> <ion-label>
<p>{{ 'addon.calendar.durationuntil' | translate }}</p> <p>{{ 'addon.calendar.durationuntil' | translate }}</p>
</ion-label> </ion-label>
@ -159,7 +159,7 @@
[placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" display-timezone="utc"> [placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" display-timezone="utc">
</ion-datetime> </ion-datetime>
</ion-item> </ion-item>
<ion-item lines="none"> <ion-item>
<ion-label> <ion-label>
<p>{{ 'addon.calendar.durationminutes' | translate }}</p> <p>{{ 'addon.calendar.durationminutes' | translate }}</p>
</ion-label> </ion-label>
@ -175,7 +175,7 @@
<!-- Repeat (for new events). --> <!-- Repeat (for new events). -->
<ng-container *ngIf="!eventId || eventId < 0"> <ng-container *ngIf="!eventId || eventId < 0">
<ion-item class="ion-text-wrap divider" lines="none"> <ion-item class="ion-text-wrap divider">
<ion-label> <ion-label>
<p class="item-heading">{{ 'addon.calendar.repeatevent' | translate }}</p> <p class="item-heading">{{ 'addon.calendar.repeatevent' | translate }}</p>
</ion-label> </ion-label>

View File

@ -23,7 +23,7 @@
</ion-card> </ion-card>
<ion-card *ngIf="plan"> <ion-card *ngIf="plan">
<ion-list> <ion-list>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.description" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.description">
<ion-label> <ion-label>
<p> <p>
<core-format-text [text]="plan.plan.description" contextLevel="user" [contextInstanceId]="plan.plan.userid"> <core-format-text [text]="plan.plan.description" contextLevel="user" [contextInstanceId]="plan.plan.userid">
@ -31,25 +31,25 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p class="item-heading">{{ 'addon.competency.status' | translate }}</p> <p class="item-heading">{{ 'addon.competency.status' | translate }}</p>
<p>{{ plan.plan.statusname }}</p> <p>{{ plan.plan.statusname }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.duedate > 0" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.duedate > 0">
<ion-label> <ion-label>
<p class="item-heading">{{ 'addon.competency.duedate' | translate }}</p> <p class="item-heading">{{ 'addon.competency.duedate' | translate }}</p>
<p>{{ plan.plan.duedate * 1000 | coreFormatDate }}</p> <p>{{ plan.plan.duedate * 1000 | coreFormatDate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.template" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.template">
<ion-label> <ion-label>
<p class="item-heading">{{ 'addon.competency.template' | translate }}</p> <p class="item-heading">{{ 'addon.competency.template' | translate }}</p>
<p>{{ plan.plan.template.shortname }}</p> <p>{{ plan.plan.template.shortname }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label id="addon-competency-plan-{{plan.plan.id}}-progress"> <ion-label id="addon-competency-plan-{{plan.plan.id}}-progress">
<p class="item-heading">{{ 'addon.competency.progress' | translate }}</p> <p class="item-heading">{{ 'addon.competency.progress' | translate }}</p>
<p> <p>

View File

@ -40,11 +40,9 @@
[attr.aria-label]="(favourites.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-label]="(favourites.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="favourites.expanded" aria-controls="addon-messages-groupconversations-favourite" role="heading" [attr.aria-expanded]="favourites.expanded" aria-controls="addon-messages-groupconversations-favourite" role="heading"
detail="false"> detail="false">
<ion-icon *ngIf="!favourites.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
class="expandable-status-icon"> [class.expandable-status-icon-expanded]="favourites.expanded">
</ion-icon> </ion-icon>
<ion-icon *ngIf="favourites.expanded" name="fas-chevron-down" slot="start" aria-hidden="true"
class="expandable-status-icon"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'core.favourites' | translate }} ({{ favourites.count }})</h2> <h2>{{ 'core.favourites' | translate }} ({{ favourites.count }})</h2>
</ion-label> </ion-label>
@ -76,9 +74,8 @@
<ion-item button class="divider ion-text-wrap" (click)="toggle(group)" sticky="true" <ion-item button class="divider ion-text-wrap" (click)="toggle(group)" sticky="true"
[attr.aria-label]="(group.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-expanded]="group.expanded" [attr.aria-label]="(group.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-expanded]="group.expanded"
aria-controls="addon-messages-groupconversations-group" role="heading" detail="false"> aria-controls="addon-messages-groupconversations-group" role="heading" detail="false">
<ion-icon *ngIf="!group.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
class="expandable-status-icon"></ion-icon> [class.expandable-status-icon-expanded]="group.expanded">
<ion-icon *ngIf="group.expanded" name="fas-chevron-down" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon> </ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</h2> <h2>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</h2>
@ -111,11 +108,9 @@
[attr.aria-label]="(individual.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-label]="(individual.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="individual.expanded" aria-controls="addon-messages-groupconversations-individual" role="heading" [attr.aria-expanded]="individual.expanded" aria-controls="addon-messages-groupconversations-individual" role="heading"
detail="false"> detail="false">
<ion-icon *ngIf="!individual.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
class="expandable-status-icon"> [class.expandable-status-icon-expanded]="individual.expanded">
</ion-icon> </ion-icon>
<ion-icon *ngIf="individual.expanded" name="fas-chevron-down" slot="start" aria-hidden="true"
class="expandable-status-icon"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</h2> <h2>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</h2>
</ion-label> </ion-label>

View File

@ -2,7 +2,7 @@
<ion-item class="ion-text-wrap" *ngIf="files && files.length && !edit"> <ion-item class="ion-text-wrap" *ngIf="files && files.length && !edit">
<ion-label> <ion-label>
<h2>{{ plugin.name }}</h2> <h2>{{ plugin.name }}</h2>
<div lines="none"> <div>
<core-files [files]="files" [component]="component" [componentId]="assign.cmid" [alwaysDownload]="true"></core-files> <core-files [files]="files" [component]="component" [componentId]="assign.cmid" [alwaysDownload]="true"></core-files>
</div> </div>
</ion-label> </ion-label>

View File

@ -11,7 +11,7 @@
</span> </span>
<ng-container *ngIf="displayMode"> <ng-container *ngIf="displayMode">
<div lines="none"> <div>
<core-files [files]="files" [component]="component" [componentId]="componentId" [alwaysDownload]="true"></core-files> <core-files [files]="files" [component]="component" [componentId]="componentId" [alwaysDownload]="true"></core-files>
</div> </div>
</ng-container> </ng-container>

View File

@ -40,7 +40,7 @@
<!-- Activity info. --> <!-- Activity info. -->
<core-course-module-info [module]="module" [description]="forum && forum.type != 'single' && description" [component]="component" <core-course-module-info [module]="module" [description]="forum && forum.type != 'single' && description" [component]="component"
[componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings">
<ion-item lines="none" class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
{{descriptionNote}} {{descriptionNote}}
</ion-label> </ion-label>

View File

@ -1,7 +1,7 @@
<div class="addon-mod_forum-post"> <div class="addon-mod_forum-post">
<ng-container *ngIf="!formData.isEditing || !showForm"> <ng-container *ngIf="!formData.isEditing || !showForm">
<ion-card-header class="ion-text-wrap ion-no-padding" id="addon-mod_forum-post-{{post.id}}"> <ion-card-header class="ion-text-wrap ion-no-padding" id="addon-mod_forum-post-{{post.id}}">
<ion-item class="ion-text-wrap" [class.highlight]="highlight" lines="none"> <ion-item class="ion-text-wrap" [class.highlight]="highlight">
<ion-label> <ion-label>
<div class="addon-mod-forum-post-title" *ngIf="displaySubject"> <div class="addon-mod-forum-post-title" *ngIf="displaySubject">
<h2 class="ion-text-wrap"> <h2 class="ion-text-wrap">
@ -63,13 +63,13 @@
<core-format-text [component]="component" [componentId]="componentId" [text]="post.message" contextLevel="module" <core-format-text [component]="component" [componentId]="componentId" [text]="post.message" contextLevel="module"
[contextInstanceId]="forum && forum.cmid" [courseId]="courseId"> [contextInstanceId]="forum && forum.cmid" [courseId]="courseId">
</core-format-text> </core-format-text>
<div lines="none" *ngIf="post.attachments && post.attachments.length > 0"> <div *ngIf="post.attachments && post.attachments.length > 0">
<core-files [files]="post.attachments" [component]="component" [componentId]="componentId" showInline="true"> <core-files [files]="post.attachments" [component]="component" [componentId]="componentId" showInline="true">
</core-files> </core-files>
</div> </div>
</ion-card-content> </ion-card-content>
<div class="addon-mod-forum-post-more-info"> <div class="addon-mod-forum-post-more-info">
<ion-item class="ion-text-wrap" *ngIf="tagsEnabled && post.tags && post.tags.length > 0" lines="none"> <ion-item class="ion-text-wrap" *ngIf="tagsEnabled && post.tags && post.tags.length > 0">
<div slot="start">{{ 'core.tag.tags' | translate }}:</div> <div slot="start">{{ 'core.tag.tags' | translate }}:</div>
<ion-label> <ion-label>
<core-tag-list [tags]="post.tags"></core-tag-list> <core-tag-list [tags]="post.tags"></core-tag-list>

View File

@ -34,10 +34,8 @@
<ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced" <ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate" role="heading" [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate" role="heading"
aria-controls="addon-mod-forum-new-discussion-advanced"> aria-controls="addon-mod-forum-new-discussion-advanced">
<ion-icon *ngIf="!advanced" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
class="expandable-status-icon"></ion-icon> [class.expandable-status-icon-expanded]="advanced"></ion-icon>
<ion-icon *ngIf="advanced" name="fas-chevron-down" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.advanced' | translate }}</h2> <h2>{{ 'addon.mod_forum.advanced' | translate }}</h2>
</ion-label> </ion-label>

View File

@ -46,7 +46,7 @@
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
<div *ngIf="entry.attachment" lines="none"> <div *ngIf="entry.attachment">
<core-file *ngFor="let file of entry.attachments" [file]="file" [component]="component" [componentId]="componentId"> <core-file *ngFor="let file of entry.attachments" [file]="file" [component]="component" [componentId]="componentId">
</core-file> </core-file>
</div> </div>

View File

@ -36,13 +36,13 @@
<!-- Attempt summary. --> <!-- Attempt summary. -->
<ion-card class="addon-mod_h5pactivity-attempt-result-summary"> <ion-card class="addon-mod_h5pactivity-attempt-result-summary">
<ion-list> <ion-list>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.startdate' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.startdate' | translate }}</h2>
<p>{{ attempt.timecreated | coreFormatDate:'strftimedatetime' }}</p> <p>{{ attempt.timecreated | coreFormatDate:'strftimedatetime' }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.completion' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.completion' | translate }}</h2>
<p *ngIf="attempt.completion"> <p *ngIf="attempt.completion">
@ -55,13 +55,13 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.duration' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.duration' | translate }}</h2>
<p>{{ attempt.durationReadable }}</p> <p>{{ attempt.durationReadable }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2>
<p *ngIf="attempt.success !== null && attempt.success"> <p *ngIf="attempt.success !== null && attempt.success">
@ -77,7 +77,7 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="attempt.maxscore" class="ion-text-wrap" lines="none"> <ion-item *ngIf="attempt.maxscore" class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.totalscore' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.totalscore' | translate }}</h2>
<p>{{ 'addon.mod_h5pactivity.score_out_of' | translate:{$a: attempt} }}</p> <p>{{ 'addon.mod_h5pactivity.score_out_of' | translate:{$a: attempt} }}</p>
@ -151,7 +151,7 @@
</ng-container> </ng-container>
<!-- Result doesn't support tracking. --> <!-- Result doesn't support tracking. -->
<ion-item class="ion-text-wrap core-warning-item" *ngIf="!result.track" lines="none"> <ion-item class="ion-text-wrap core-warning-item" *ngIf="!result.track">
<ion-icon slot="start" name="fas-exclamation-triangle" color="warning" aria-hidden="true"></ion-icon> <ion-icon slot="start" name="fas-exclamation-triangle" color="warning" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
{{ 'addon.mod_h5pactivity.no_compatible_track' | translate:{$a: result.interactiontype} }} {{ 'addon.mod_h5pactivity.no_compatible_track' | translate:{$a: result.interactiontype} }}

View File

@ -69,7 +69,7 @@
<ion-list *ngIf="(lesson && !preventReasons.length) || retakeToReview"> <ion-list *ngIf="(lesson && !preventReasons.length) || retakeToReview">
<ng-container *ngIf="retakeToReview"> <ng-container *ngIf="retakeToReview">
<!-- A retake was finished in a synchronization, allow reviewing it. --> <!-- A retake was finished in a synchronization, allow reviewing it. -->
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label class="ion-padding-bottom"> <ion-label class="ion-padding-bottom">
{{ 'addon.mod_lesson.retakefinishedinsync' | translate }} {{ 'addon.mod_lesson.retakefinishedinsync' | translate }}
</ion-label> </ion-label>

View File

@ -198,34 +198,34 @@
<ion-card-header class="ion-text-wrap" *ngIf="eolData.gradelesson"> <ion-card-header class="ion-text-wrap" *ngIf="eolData.gradelesson">
<ion-card-title>{{ 'addon.mod_lesson.congratulations' | translate }}</ion-card-title> <ion-card-title>{{ 'addon.mod_lesson.congratulations' | translate }}</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-item class="ion-text-wrap" *ngIf="eolData.notenoughtimespent" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.notenoughtimespent">
<ion-label>{{ eolData.notenoughtimespent.message }}</ion-label> <ion-label>{{ eolData.notenoughtimespent.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.numberofpagesviewed" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.numberofpagesviewed">
<ion-label>{{ eolData.numberofpagesviewed.message }}</ion-label> <ion-label>{{ eolData.numberofpagesviewed.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.youshouldview" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.youshouldview">
<ion-label>{{ eolData.youshouldview.message }}</ion-label> <ion-label>{{ eolData.youshouldview.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.numberofcorrectanswers" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.numberofcorrectanswers">
<ion-label>{{ eolData.numberofcorrectanswers.message }}</ion-label> <ion-label>{{ eolData.numberofcorrectanswers.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.displayscorewithessays" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.displayscorewithessays">
<ion-label [innerHTML]="eolData.displayscorewithessays.message"></ion-label> <ion-label [innerHTML]="eolData.displayscorewithessays.message"></ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="!eolData.displayscorewithessays && eolData.displayscorewithoutessays" lines="none"> <ion-item class="ion-text-wrap" *ngIf="!eolData.displayscorewithessays && eolData.displayscorewithoutessays">
<ion-label>{{ eolData.displayscorewithoutessays.message }}</ion-label> <ion-label>{{ eolData.displayscorewithoutessays.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.yourcurrentgradeisoutof" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.yourcurrentgradeisoutof">
<ion-label>{{ eolData.yourcurrentgradeisoutof.message }}</ion-label> <ion-label>{{ eolData.yourcurrentgradeisoutof.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.eolstudentoutoftimenoanswers" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.eolstudentoutoftimenoanswers">
<ion-label>{{ eolData.eolstudentoutoftimenoanswers.message }}</ion-label> <ion-label>{{ eolData.eolstudentoutoftimenoanswers.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.welldone" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.welldone">
<ion-label>{{ eolData.welldone.message }}</ion-label> <ion-label>{{ eolData.welldone.message }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="lesson.progressbar && eolData.progresscompleted" lines="none"> <ion-item class="ion-text-wrap" *ngIf="lesson.progressbar && eolData.progresscompleted">
<ion-label> <ion-label>
<span id="addon-mod_lesson-{{cmId}}-progress-end"> <span id="addon-mod_lesson-{{cmId}}-progress-end">
{{ 'addon.mod_lesson.progresscompleted' | translate:{$a: eolData.progresscompleted.value} }} {{ 'addon.mod_lesson.progresscompleted' | translate:{$a: eolData.progresscompleted.value} }}
@ -235,14 +235,14 @@
</core-progress-bar> </core-progress-bar>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="eolData.displayofgrade" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.displayofgrade">
<ion-label>{{ eolData.displayofgrade.message }}</ion-label> <ion-label>{{ eolData.displayofgrade.message }}</ion-label>
</ion-item> </ion-item>
<ion-button *ngIf="eolData.reviewlesson" expand="block" class="ion-text-wrap ion-margin button-no-uppercase" <ion-button *ngIf="eolData.reviewlesson" expand="block" class="ion-text-wrap ion-margin button-no-uppercase"
(click)="reviewLesson(reviewPageId!)"> (click)="reviewLesson(reviewPageId!)">
{{ 'addon.mod_lesson.reviewlesson' | translate }} {{ 'addon.mod_lesson.reviewlesson' | translate }}
</ion-button> </ion-button>
<ion-item class="ion-text-wrap" *ngIf="eolData.modattemptsnoteacher" lines="none"> <ion-item class="ion-text-wrap" *ngIf="eolData.modattemptsnoteacher">
<ion-label>{{ eolData.modattemptsnoteacher.message }}</ion-label> <ion-label>{{ eolData.modattemptsnoteacher.message }}</ion-label>
</ion-item> </ion-item>
<!-- If activity link was successfully formatted, render the button. --> <!-- If activity link was successfully formatted, render the button. -->
@ -252,7 +252,7 @@
[courseId]="courseId"> [courseId]="courseId">
</core-format-text> </core-format-text>
</ion-button> </ion-button>
<ion-item class="ion-text-wrap" *ngIf="activityLink && !activityLink.formatted" lines="none"> <ion-item class="ion-text-wrap" *ngIf="activityLink && !activityLink.formatted">
<!-- Activity link wasn't formatted, render the original link. --> <!-- Activity link wasn't formatted, render the original link. -->
<ion-label> <ion-label>
<core-format-text [text]="activityLink.label" contextLevel="module" [contextInstanceId]="lesson?.coursemodule" <core-format-text [text]="activityLink.label" contextLevel="module" [contextInstanceId]="lesson?.coursemodule"

View File

@ -83,7 +83,7 @@
<ion-card-header class="ion-text-wrap"> <ion-card-header class="ion-text-wrap">
<ion-card-title>{{page.qtype}}: {{page.title}}</ion-card-title> <ion-card-title>{{page.qtype}}: {{page.title}}</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'addon.mod_lesson.question' | translate }}</h3> <h3 class="item-heading">{{ 'addon.mod_lesson.question' | translate }}</h3>
<p> <p>
@ -94,13 +94,12 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'addon.mod_lesson.answer' | translate }}</h3> <h3 class="item-heading">{{ 'addon.mod_lesson.answer' | translate }}</h3>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none" <ion-item class="ion-text-wrap" *ngIf="!page.answerdata || !page.answerdata.answers || !page.answerdata.answers.length">
*ngIf="!page.answerdata || !page.answerdata.answers || !page.answerdata.answers.length">
<ion-label> <ion-label>
<p>{{ 'addon.mod_lesson.didnotanswerquestion' | translate }}</p> <p>{{ 'addon.mod_lesson.didnotanswerquestion' | translate }}</p>
</ion-label> </ion-label>
@ -108,7 +107,7 @@
<div *ngIf="page.answerdata && page.answerdata.answers && page.answerdata.answers.length" <div *ngIf="page.answerdata && page.answerdata.answers && page.answerdata.answers.length"
class="addon-mod_lesson-answer"> class="addon-mod_lesson-answer">
<ng-container *ngFor="let answer of page.answerdata.answers"> <ng-container *ngFor="let answer of page.answerdata.answers">
<ion-item lines="none" *ngIf="page.isContent"> <ion-item *ngIf="page.isContent">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<ion-grid class="ion-no-padding"> <ion-grid class="ion-no-padding">
<!-- Content page, display a button and the content. --> <!-- Content page, display a button and the content. -->
@ -151,7 +150,7 @@
</ion-item> </ion-item>
<!-- Short answer or numeric. --> <!-- Short answer or numeric. -->
<ion-item class="ion-text-wrap" *ngIf="answer[0].isText" lines="none"> <ion-item class="ion-text-wrap" *ngIf="answer[0].isText">
<ion-label> <ion-label>
<p>{{ answer[0].value }}</p> <p>{{ answer[0].value }}</p>
<ion-badge *ngIf="answer[1]" color="dark"> <ion-badge *ngIf="answer[1]" color="dark">
@ -164,7 +163,7 @@
</ion-item> </ion-item>
<!-- Matching. --> <!-- Matching. -->
<ion-item lines="none" *ngIf="answer[0].isSelect"> <ion-item *ngIf="answer[0].isSelect">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<ion-grid class="ion-no-padding"> <ion-grid class="ion-no-padding">
<ion-row> <ion-row>
@ -191,8 +190,7 @@
</ion-item> </ion-item>
<!-- Essay or couldn't determine. --> <!-- Essay or couldn't determine. -->
<ion-item class="ion-text-wrap" lines="none" <ion-item class="ion-text-wrap" *ngIf="!answer[0].isCheckbox && !answer[0].isText && !answer[0].isSelect">
*ngIf="!answer[0].isCheckbox && !answer[0].isText && !answer[0].isSelect">
<ion-label> <ion-label>
<p> <p>
<core-format-text [component]="component" [componentId]="lesson?.coursemodule" <core-format-text [component]="component" [componentId]="lesson?.coursemodule"
@ -210,7 +208,7 @@
</ion-item> </ion-item>
</ng-container> </ng-container>
<ion-item class="ion-text-wrap" *ngIf="!page.isContent && !page.isQuestion" lines="none"> <ion-item class="ion-text-wrap" *ngIf="!page.isContent && !page.isQuestion">
<!-- Another page (end of branch, ...). --> <!-- Another page (end of branch, ...). -->
<ion-label> <ion-label>
<p> <p>
@ -227,7 +225,7 @@
</ion-item> </ion-item>
</ng-container> </ng-container>
<ion-item class="ion-text-wrap" *ngIf="page.answerdata.response" lines="none"> <ion-item class="ion-text-wrap" *ngIf="page.answerdata.response">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'addon.mod_lesson.response' | translate }}</h3> <h3 class="item-heading">{{ 'addon.mod_lesson.response' | translate }}</h3>
<p> <p>

View File

@ -17,7 +17,7 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-list *ngIf="attempt" lines="none"> <ion-list *ngIf="attempt">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_quiz.attemptnumber' | translate }}</h2> <h2>{{ 'addon.mod_quiz.attemptnumber' | translate }}</h2>

View File

@ -31,7 +31,7 @@
</ng-container> </ng-container>
</ion-card-title> </ion-card-title>
</ion-card-header> </ion-card-header>
<ion-list lines="none"> <ion-list>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'addon.mod_quiz.startedon' | translate }}</h2> <h2>{{ 'addon.mod_quiz.startedon' | translate }}</h2>

View File

@ -57,7 +57,7 @@
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="!edit && workshop.overallfeedbackfiles && data.assessment?.feedbackattachmentfiles?.length" lines="none"> <ion-item *ngIf="!edit && workshop.overallfeedbackfiles && data.assessment?.feedbackattachmentfiles?.length">
<ion-label> <ion-label>
<core-files [files]="data.assessment?.feedbackattachmentfiles" [component]="component" [componentId]="componentId"> <core-files [files]="data.assessment?.feedbackattachmentfiles" [component]="component" [componentId]="componentId">
</core-files> </core-files>

View File

@ -26,7 +26,7 @@
</div> </div>
<ion-card *ngFor="let notification of notifications"> <ion-card *ngFor="let notification of notifications">
<ion-item class="ion-text-wrap" lines="none" [attr.aria-label]=" <ion-item class="ion-text-wrap" [attr.aria-label]="
notification.timeread notification.timeread
? notification.subject ? notification.subject
: 'addon.notifications.unreadnotification' | translate: {$a: notification.subject}"> : 'addon.notifications.unreadnotification' | translate: {$a: notification.subject}">

View File

@ -10,7 +10,7 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-item class="ion-text-wrap" lines="none" [attr.aria-label]="subject"> <ion-item class="ion-text-wrap" [attr.aria-label]="subject">
<core-user-avatar *ngIf="userIdFrom > 0" slot="start" [userId]="userIdFrom" [profileUrl]="profileImageUrlFrom" <core-user-avatar *ngIf="userIdFrom > 0" slot="start" [userId]="userIdFrom" [profileUrl]="profileImageUrlFrom"
[fullname]="userFromFullName"> [fullname]="userFromFullName">
<img *ngIf="iconUrl && !modname" [src]="iconUrl" alt="" role="presentation" class="core-avatar-extra"> <img *ngIf="iconUrl && !modname" [src]="iconUrl" alt="" role="presentation" class="core-avatar-extra">

View File

@ -107,11 +107,11 @@
</ion-item> </ion-item>
<!-- Phone view --> <!-- Phone view -->
<ion-item class="ion-text-wrap ion-no-margin ion-hide-md-up" lines="none"> <ion-item class="ion-text-wrap ion-no-margin ion-hide-md-up">
<p class="item-heading">{{ notification.displayname }}</p> <p class="item-heading">{{ notification.displayname }}</p>
</ion-item> </ion-item>
<!-- If notifications enabled, show toggles. If disabled, show "Disabled" instead of toggle. --> <!-- If notifications enabled, show toggles. If disabled, show "Disabled" instead of toggle. -->
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" class="ion-text-wrap ion-hide-md-up" lines="none"> <ion-item *ngFor="let state of ['loggedin', 'loggedoff']" class="ion-text-wrap ion-hide-md-up">
<ion-label class="ion-margin-horizontal"> <ion-label class="ion-margin-horizontal">
<p>{{ 'core.settings.' + state | translate }}</p> <p>{{ 'core.settings.' + state | translate }}</p>
</ion-label> </ion-label>
@ -148,7 +148,7 @@
</ion-item-divider> </ion-item-divider>
<ng-container *ngFor="let notification of component.notifications"> <ng-container *ngFor="let notification of component.notifications">
<!-- If notifications enabled, show toggles. If disabled, show "Disabled" instead of toggle. --> <!-- If notifications enabled, show toggles. If disabled, show "Disabled" instead of toggle. -->
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p>{{ notification.displayname }}</p> <p>{{ notification.displayname }}</p>
</ion-label> </ion-label>

View File

@ -14,7 +14,7 @@
<ion-card-header> <ion-card-header>
<p class="ion-text-wrap ion-no-margin">{{ 'addon.storagemanager.courseinfo' | translate }}</p> <p class="ion-text-wrap ion-no-margin">{{ 'addon.storagemanager.courseinfo' | translate }}</p>
<ion-card-title>{{ title }}</ion-card-title> <ion-card-title>{{ title }}</ion-card-title>
<ion-item class="size ion-text-wrap ion-no-padding" lines="none"> <ion-item class="size ion-text-wrap ion-no-padding">
<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-badge color="light">{{ totalSize | coreBytesToSize }}</ion-badge> <ion-badge color="light">{{ totalSize | coreBytesToSize }}</ion-badge>

View File

@ -26,7 +26,7 @@
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
</ion-item> </ion-item>
<ion-item class="size ion-text-wrap" lines="none"> <ion-item class="size ion-text-wrap">
<ion-label> <ion-label>
<h2 class="ion-text-wrap">{{ 'addon.storagemanager.coursesspaceusage' | translate }}</h2> <h2 class="ion-text-wrap">{{ 'addon.storagemanager.coursesspaceusage' | translate }}</h2>
<ion-badge color="light">{{ totalSize | coreBytesToSize }}</ion-badge> <ion-badge color="light">{{ totalSize | coreBytesToSize }}</ion-badge>

View File

@ -20,9 +20,7 @@
@include safe-area-padding-end(null, 0px); @include safe-area-padding-end(null, 0px);
height: var(--height); height: var(--height);
color: var(--tabs-color); color: var(--tabs-color);
-webkit-filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow))); border-bottom: 1px solid var(--stroke);
filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
border: 0;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;

View File

@ -1,4 +1,4 @@
<ion-item lines="none" class="core-timer" role="timer" [ngClass]="{'ion-text-center': align == 'center', 'ion-text-end': align == 'right'}"> <ion-item class="core-timer" role="timer" [ngClass]="{'ion-text-center': align == 'center', 'ion-text-end': align == 'right'}">
<ion-icon name="fas-clock" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-clock" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<span *ngIf="timeLeft && timeLeft > 0 && timerText" class="core-timer-text">{{ timerText }}</span> <span *ngIf="timeLeft && timeLeft > 0 && timerText" class="core-timer-text">{{ timerText }}</span>

View File

@ -0,0 +1,187 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons';
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
const defaultMaxHeight = 56;
const buttonHeight = 44;
/**
* Directive to make an element collapsible.
*
* Example usage:
*
* <div collapsible-item>
*/
@Directive({
selector: '[collapsible-item]',
})
export class CoreCollapsibleItemDirective implements OnInit {
/**
* Max height in pixels to render the content box. It should be 56 at least to make sense.
* Using this parameter will force display: block to calculate height better.
* If you want to avoid this use class="inline" at the same time to use display: inline-block.
*/
@Input('collapsible-item') height: number | string = defaultMaxHeight;
protected element: HTMLElement;
protected toggleExpandEnabled = false;
protected expanded = false;
protected maxHeight = defaultMaxHeight;
protected loadingChangedListener?: CoreEventObserver;
constructor(el: ElementRef<HTMLElement>) {
this.element = el.nativeElement;
this.element.addEventListener('click', this.elementClicked.bind(this));
}
/**
* @inheritdoc
*/
ngOnInit(): void {
if (typeof this.height === 'string') {
this.maxHeight = this.height === ''
? defaultMaxHeight
: parseInt(this.height, 10);
} else {
this.maxHeight = this.height;
}
this.maxHeight = this.maxHeight < defaultMaxHeight ? defaultMaxHeight : this.maxHeight;
if (!this.maxHeight || (window.innerWidth > 576 && window.innerHeight > 576)) {
// Do not collapse on big screens.
return;
}
// Calculate the height now.
this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
this.setExpandButtonEnabled(false);
// Recalculate the height if a parent core-loading displays the content.
this.loadingChangedListener =
CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, (data: CoreEventLoadingChangedData) => {
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) {
// The format-text is inside the loading, re-calculate the height.
this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200);
}
});
}
/**
* Calculate the height and check if we need to display show more or not.
*/
protected calculateHeight(): void {
// @todo: Work on calculate this height better.
if (!this.maxHeight) {
return;
}
// Remove max-height (if any) to calculate the real height.
const initialMaxHeight = this.element.style.maxHeight;
this.element.style.maxHeight = '';
const height = CoreDomUtils.getElementHeight(this.element) || 0;
// Restore the max height now.
this.element.style.maxHeight = initialMaxHeight;
// If cannot calculate height, shorten always.
this.setExpandButtonEnabled(!height || height > this.maxHeight);
}
/**
* Sets if expand button is enabled or not.
*
* @param enable Wether enable or disable.
*/
protected setExpandButtonEnabled(enable: boolean): void {
this.toggleExpandEnabled = enable;
this.element.classList.toggle('collapsible-enabled', enable);
if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) {
return;
}
// Add expand/collapse buttons
const toggleButton = document.createElement('ion-button');
toggleButton.classList.add('collapsible-toggle');
toggleButton.setAttribute('fill', 'clear');
const toggleText = document.createElement('span');
toggleText.classList.add('collapsible-toggle-text');
toggleButton.appendChild(toggleText);
const expandArrow = document.createElement('span');
expandArrow.classList.add('collapsible-toggle-arrow');
toggleButton.appendChild(expandArrow);
this.element.appendChild(toggleButton);
this.toggleExpand(this.expanded);
}
/**
* Expand or collapse text.
*
* @param expand Wether expand or collapse text. If undefined, will toggle.
*/
protected toggleExpand(expand?: boolean): void {
if (expand === undefined) {
expand = !this.expanded;
}
this.expanded = expand;
this.element.classList.toggle('collapsible-expanded', expand);
this.element.classList.toggle('collapsible-collapsed', !expand);
this.element.style.maxHeight = expand ? '' : (this.maxHeight + buttonHeight) + 'px';
const toggleButton = this.element.querySelector('ion-button.collapsible-toggle');
const toggleText = toggleButton?.querySelector('.collapsible-toggle-text');
if (!toggleButton || !toggleText) {
return;
}
toggleText.innerHTML = expand ? Translate.instant('core.showless') : Translate.instant('core.showmore');
toggleButton.setAttribute('aria-expanded', expand ? 'true' : 'false');
}
/**
* Listener to call when the element is clicked.
*
* @param e Click event.
*/
protected elementClicked(e: MouseEvent): void {
if (e.defaultPrevented) {
// Ignore it if the event was prevented by some other listener.
return;
}
if (!this.toggleExpandEnabled) {
// Nothing to do on click, just stop.
return;
}
e.preventDefault();
e.stopPropagation();
this.toggleExpand();
}
}

View File

@ -29,6 +29,7 @@ import { CoreOnResizeDirective } from './on-resize';
import { CoreDownloadFileDirective } from './download-file'; import { CoreDownloadFileDirective } from './download-file';
import { CoreCollapsibleHeaderDirective } from './collapsible-header'; import { CoreCollapsibleHeaderDirective } from './collapsible-header';
import { CoreSwipeNavigationDirective } from './swipe-navigation'; import { CoreSwipeNavigationDirective } from './swipe-navigation';
import { CoreCollapsibleItemDirective } from './collapsible-item';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -47,6 +48,7 @@ import { CoreSwipeNavigationDirective } from './swipe-navigation';
CoreDownloadFileDirective, CoreDownloadFileDirective,
CoreCollapsibleHeaderDirective, CoreCollapsibleHeaderDirective,
CoreSwipeNavigationDirective, CoreSwipeNavigationDirective,
CoreCollapsibleItemDirective,
], ],
exports: [ exports: [
CoreAutoFocusDirective, CoreAutoFocusDirective,
@ -64,6 +66,7 @@ import { CoreSwipeNavigationDirective } from './swipe-navigation';
CoreDownloadFileDirective, CoreDownloadFileDirective,
CoreCollapsibleHeaderDirective, CoreCollapsibleHeaderDirective,
CoreSwipeNavigationDirective, CoreSwipeNavigationDirective,
CoreCollapsibleItemDirective,
], ],
}) })
export class CoreDirectivesModule {} export class CoreDirectivesModule {}

View File

@ -1,4 +1,4 @@
<!-- Only render the block if it's supported. --> <!-- Only render the block if it's supported. -->
<div *ngIf="loaded && componentClass && block.visible" class="{{class}}"> <ion-card *ngIf="loaded && componentClass && block.visible" class="{{class}}">
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component> <core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
</div> </ion-card>

View File

@ -13,31 +13,32 @@
<ion-content> <ion-content>
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label"> <ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
<ng-container *ngFor="let section of sectionsToRender"> <ng-container *ngFor="let section of sectionsToRender">
<ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider core-course-index-all" <ion-item *ngIf="allSectionId == section.id" class="divider core-course-index-all"
(click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false"> (click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false">
<ion-label> <ion-label>
<p class="item-heading"> <h2>
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id"> <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
</core-format-text> </core-format-text>
</p> </h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ng-container *ngIf="allSectionId != section.id"> <ng-container *ngIf="allSectionId != section.id">
<ion-item class="ion-text-wrap divider section" (click)="selectSectionOrModule($event, section.id)" button <ion-item class="divider section" (click)="selectSectionOrModule($event, section.id)" button
[class.item-current]="selectedId === section.id" [class.item-dimmed]="section.visible === 0" detail="false" [class.item-current]="selectedId === section.id" [class.item-dimmed]="section.visible === 0" detail="false"
sticky="true"> sticky="true">
<ion-icon *ngIf="section.hasVisibleModules" [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" <ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start"
flip-rtl slot="start" class="expandable-status-icon" (click)="toggleExpand($event, section)" class="expandable-status-icon" (click)="toggleExpand($event, section)"
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"> [attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"
[class.expandable-status-icon-expanded]="section.expanded">
</ion-icon> </ion-icon>
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon"> <ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon> </ion-icon>
<ion-label> <ion-label>
<p class="item-heading"> <h2>
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id"> <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
</core-format-text> </core-format-text>
</p> </h2>
</ion-label> </ion-label>
<ion-badge *ngIf="section.highlighted && highlighted">{{highlighted}}</ion-badge> <ion-badge *ngIf="section.highlighted && highlighted">{{highlighted}}</ion-badge>
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted" <ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
@ -45,8 +46,8 @@
</ion-item> </ion-item>
<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.item-dimmed]="!module.visible" (click)="selectSectionOrModule($event, section.id, module.id)" <ion-item class="module" [class.item-dimmed]="!module.visible"
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" *ngIf="module.completionStatus === 0"

View File

@ -16,21 +16,31 @@ ion-icon.completioninfo {
width: 18px; width: 18px;
} }
ion-item::part(native) { ion-item.module::part(native) {
--padding-start: 0; --padding-start: 0;
} }
ion-icon { ion-item.module ion-icon {
margin: 0; margin: 0;
@include padding(12px, 32px, 12px, 16px); @include padding(12px, 16px, 12px, 16px);
} }
ion-item.core-course-index-all::part(native) { ion-item.core-course-index-all::part(native) {
--padding-start: 16px; --padding-start: 16px;
} }
ion-item.item-current ion-icon.expandable-status-icon { ion-item.item.item-current {
@include padding(null, null, null, 11px); --background: var(--primary);
--color: var(--primary-contrast);
border: 0;
ion-badge {
border: 1px solid var(--primary-contrast);
}
::ng-deep ion-icon {
color: var(--primary-contrast);
}
} }
ion-icon.restricted { ion-icon.restricted {

View File

@ -23,7 +23,6 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { IonContent } from '@ionic/angular'; import { IonContent } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons'; import { ModalController } from '@singletons';
/** /**
@ -62,12 +61,11 @@ export class CoreCourseCourseIndexComponent implements OnInit {
} }
let completionEnabled = !!this.course.enablecompletion; let completionEnabled = !!this.course.enablecompletion;
if (completionEnabled && 'courseformatoptions' in this.course && this.course.courseformatoptions) { if (completionEnabled && 'completionusertracked' in this.course && this.course.completionusertracked !== undefined) {
const formatOptions = CoreUtils.objectToKeyValueMap(this.course.courseformatoptions, 'name', 'value'); completionEnabled = this.course.completionusertracked;
}
if (formatOptions) { if (completionEnabled && 'showcompletionconditions' in this.course && this.course.showcompletionconditions !== undefined) {
completionEnabled = !!formatOptions.completionusertracked; completionEnabled = this.course.showcompletionconditions;
}
} }
const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections); const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections);

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end" *ngIf="loaded"> <core-navbar-buttons slot="end" *ngIf="loaded">
<core-context-menu> <core-context-menu>
<core-context-menu-item [hidden]="!displayCourseIndex || !sections || !sections.length" [priority]="500" <core-context-menu-item [hidden]="!displayCourseIndex || !sections || !sections.length" [priority]="500"
[content]="'core.course.courseindex' | translate" (action)="openCourseIndex()" iconAction="menu"> [content]="'core.course.courseindex' | translate" (action)="openCourseIndex()" iconAction="fas-list-ul">
</core-context-menu-item> </core-context-menu-item>
</core-context-menu> </core-context-menu>
</core-navbar-buttons> </core-navbar-buttons>
@ -66,25 +66,22 @@
<!-- Template to render a section. --> <!-- Template to render a section. -->
<ng-template #sectionTemplate let-section="section"> <ng-template #sectionTemplate let-section="section">
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId" <section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId"
class="section-wrapper" [id]="section.id"> class="core-course-module-list-wrapper" [id]="section.id">
<ion-item-divider class="course-section ion-text-wrap" color="light" <ion-item-divider class="course-section ion-text-wrap" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
<ion-label> <ion-label>
<h2 *ngIf="section.name"> <h2 *ngIf="section.name" class="big">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id"> <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id">
</core-format-text> </core-format-text>
</h2> </h2>
<div *ngIf="section.visible === 0 && section.uservisible !== false"> <div *ngIf="section.visible === 0 && section.uservisible !== false">
<ion-chip> <ion-badge color="warning">
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon> {{ 'core.course.hiddenfromstudents' | translate }}
<ion-label>{{ 'core.course.hiddenfromstudents' | translate }}</ion-label> </ion-badge>
</ion-chip>
</div> </div>
<div *ngIf="section.visible === 0 && section.uservisible === false"> <div *ngIf="section.visible === 0 && section.uservisible === false">
<ion-chip> <ion-badge color="warning">
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon> {{ 'core.notavailable' | translate }}
<ion-label>{{ 'core.notavailable' | translate }}</ion-label> </ion-badge>
</ion-chip>
</div> </div>
<div *ngIf="section.availabilityinfo"> <div *ngIf="section.availabilityinfo">
<ion-chip> <ion-chip>
@ -99,7 +96,7 @@
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge> <ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
</ion-item-divider> </ion-item-divider>
<ion-item class="ion-text-wrap" *ngIf="section.summary" lines="none"> <ion-item class="ion-text-wrap" *ngIf="section.summary">
<ion-label> <ion-label>
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id"> <core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id">
</core-format-text> </core-format-text>

View File

@ -10,3 +10,8 @@
flex: 1; flex: 1;
} }
} }
.course-section {
--padding-start: 12px;
--inner-padding-end: 12px;
}

View File

@ -1,38 +1,63 @@
<div *ngIf="showCompletionConditions && completion && completion.isautomatic" class="core-module-automatic-completion-conditions" <ng-container *ngIf="completion">
role="list" [attr.aria-label]="'core.course.completionrequirements' | translate:{ $a: moduleName }"> <ng-container *ngIf="showCompletionConditions && completion.isautomatic">
<div *ngIf="mode == 'full'" class="core-module-automatic-completion-conditions" role="list"
[attr.aria-label]="'core.course.completionrequirements' | translate:{ $a: moduleName }">
<ng-container *ngIf="completion.istrackeduser"> <ng-container *ngIf="completion.istrackeduser">
<ng-container *ngFor="let rule of details"> <ng-container *ngFor="let rule of details">
<ion-chip *ngIf="rule.statuscomplete" color="success" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statuscomplete" color="success" role="listitem" [attr.aria-label]="rule.accessibleDescription">
<ion-icon name="fas-check" aria-hidden="true"></ion-icon> <ion-icon name="fas-check" [attr.aria-label]="'core.course.completion_automatic:done' | translate "></ion-icon>
<ion-label><strong>{{ 'core.course.completion_automatic:done' | translate }}</strong> {{ rule.rulevalue.description }} <ion-label>
</ion-label> {{ rule.rulevalue.description }}
</ion-chip> </ion-label>
</ion-chip>
<ion-chip *ngIf="rule.statuscompletefail" color="danger" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statuscompletefail" color="danger" role="listitem" [attr.aria-label]="rule.accessibleDescription">
<ion-icon name="fas-times" aria-hidden="true"></ion-icon> <ion-icon name="fas-times" [attr.aria-label]="'core.course.completion_automatic:failed' | translate "></ion-icon>
<ion-label><strong>{{ 'core.course.completion_automatic:failed' | translate }}</strong> {{ rule.rulevalue.description }} <ion-label>
</ion-label> {{ rule.rulevalue.description }}
</ion-chip> </ion-label>
</ion-chip>
<ion-chip *ngIf="rule.statusincomplete" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statusincomplete" color="dark" role="listitem" [attr.aria-label]="rule.accessibleDescription">
<ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon>
<ion-label>
{{ rule.rulevalue.description }}
</ion-label>
</ion-chip>
</ng-container>
</ng-container>
<ng-container *ngIf="!completion.istrackeduser">
<ion-chip *ngFor="let rule of details" role="listitem">
<ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon>
<ion-label>
{{ rule.rulevalue.description }}
</ion-label>
</ion-chip>
</ng-container>
</div>
<ng-container *ngIf="mode == 'basic' && completion.istrackeduser">
<ion-chip class="completioninfo completion_incomplete" *ngIf="completionStatus === 0" color="dark">
<ion-icon name="fas-edit" aria-hidden="true"></ion-icon> <ion-icon name="fas-edit" aria-hidden="true"></ion-icon>
<ion-label><strong>{{ 'core.course.completion_automatic:todo' | translate }}</strong> {{ rule.rulevalue.description }} <ion-label>
{{ 'core.course.todo' | translate }}
</ion-label> </ion-label>
</ion-chip> </ion-chip>
<ion-chip class="completioninfo completion_complete" *ngIf="completionStatus === 1 || completionStatus === 2" color="success">
<ion-icon name="fas-check" aria-hidden="true"></ion-icon>
<ion-label>{{'core.course.done' | translate }}</ion-label>
</ion-chip>
<ion-chip class="completioninfo completion_fail" *ngIf="completionStatus === 3" color="danger">
<ion-icon name="fas-times" aria-hidden="true"></ion-icon>
<ion-label>{{'core.course.failed' | translate }}</ion-label>
</ion-chip>
</ng-container> </ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="!completion.istrackeduser">
<ion-chip *ngFor="let rule of details" role="listitem">
<ion-icon name="fas-edit" aria-hidden="true"></ion-icon>
<ion-label>
<strong>{{ 'core.course.completion_automatic:todo' | translate }}</strong> {{ rule.rulevalue.description }}
</ion-label>
</ion-chip>
</ng-container>
</div>
<core-course-module-manual-completion *ngIf="showManualCompletion" [completion]="completion" [moduleName]="moduleName" <core-course-module-manual-completion *ngIf="showManualCompletion" [completion]="completion" [moduleName]="moduleName"
(completionChanged)="completionChanged.emit($event)"> (completionChanged)="completionChanged.emit($event)" [mode]="mode">
</core-course-module-manual-completion> </core-course-module-manual-completion>
</ng-container>

View File

@ -15,7 +15,12 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion'; import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion';
import { CoreCourseModuleCompletionStatus, CoreCourseModuleWSRuleDetails } from '@features/course/services/course'; import {
CoreCourseCompletionMode,
CoreCourseModuleCompletionStatus,
CoreCourseModuleCompletionTracking,
CoreCourseModuleWSRuleDetails,
} from '@features/course/services/course';
import { CoreUser } from '@features/user/services/user'; import { CoreUser } from '@features/user/services/user';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
@ -37,9 +42,11 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
@Input() showCompletionConditions = false; // Whether to show activity completion conditions. @Input() showCompletionConditions = false; // Whether to show activity completion conditions.
@Input() showManualCompletion = false; // Whether to show manual completion. @Input() showManualCompletion = false; // Whether to show manual completion.
@Input() mode: CoreCourseCompletionMode = CoreCourseCompletionMode.FULL; // Show full completion status or a basic mode.
details?: CompletionRule[]; details?: CompletionRule[];
accessibleDescription: string | null = null; accessibleDescription: string | null = null;
completionStatus?: CoreCourseModuleCompletionStatus;
/** /**
* @inheritdoc * @inheritdoc
@ -49,6 +56,11 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
return; return;
} }
this.completionStatus = !this.completion?.istrackeduser ||
this.completion.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: this.completion.state;
// Format rules. // Format rules.
this.details = await Promise.all(this.completion.details.map(async (rule: CompletionRule) => { this.details = await Promise.all(this.completion.details.map(async (rule: CompletionRule) => {
rule.statuscomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || rule.statuscomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE ||
@ -57,8 +69,8 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
rule.statusincomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE; rule.statusincomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
rule.accessibleDescription = null; rule.accessibleDescription = null;
if (this.completion!.overrideby) { if (this.completion?.overrideby) {
const fullName = await CoreUser.getUserFullNameWithDefault(this.completion!.overrideby, this.completion!.courseId); const fullName = await CoreUser.getUserFullNameWithDefault(this.completion.overrideby, this.completion.courseId);
const setByData = { const setByData = {
$a: { $a: {

View File

@ -1,4 +1,4 @@
<ion-item class="ion-text-wrap collapsible-title" lines="none"> <ion-item class="ion-text-wrap collapsible-title">
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance"> <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance">
</core-mod-icon> </core-mod-icon>
<ion-label> <ion-label>
@ -11,7 +11,7 @@
<ng-content select="[title]"></ng-content> <ng-content select="[title]"></ng-content>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="description" lines="none"> <ion-item class="ion-text-wrap" *ngIf="description">
<ion-label> <ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module" <core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="expandDescription ? null : 120"> [contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="expandDescription ? null : 120">
@ -20,7 +20,7 @@
</ion-item> </ion-item>
<ng-content select="[description]"></ng-content> <ng-content select="[description]"></ng-content>
<ion-item class="ion-text-wrap core-module-dates" lines="none" *ngIf="showCompletion && (module.dates?.length || <ion-item class="ion-text-wrap" *ngIf="showCompletion && (module.dates?.length ||
(module.completiondata && module.completiondata.isautomatic && module.uservisible))"> (module.completiondata && module.completiondata.isautomatic && module.uservisible))">
<ion-label> <ion-label>
<!-- Activity dates. --> <!-- Activity dates. -->

View File

@ -2,7 +2,6 @@
:host { :host {
display: block; display: block;
box-shadow: 0px 3px 3px rgba(var(--drop-shadow));
margin-bottom: 8px; margin-bottom: 8px;
padding-bottom: 1px; // To allow margins inside. padding-bottom: 1px; // To allow margins inside.
background-color: var(--contrast-background); background-color: var(--contrast-background);
@ -17,8 +16,14 @@
align-self: flex-start; align-self: flex-start;
} }
.core-module-dates ion-icon { .core-module-dates {
@include margin-horizontal(null, 8px); background: var(--light);
border-radius: var(--small-radius);
padding: 8px;
ion-icon {
@include margin-horizontal(null, 8px);
}
} }
} }

View File

@ -1,24 +1,23 @@
<div *ngIf="completion && !completion.isautomatic" class="core-module-manual-completion"> <ng-container *ngIf="completion && !completion.isautomatic">
<ng-container *ngIf="completion.istrackeduser"> <ng-container *ngIf="completion.istrackeduser">
<ng-container *ngIf="completion.state"> <ion-button *ngIf="completion.state" color="success" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)"
<ion-button color="success" fill="outline" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)" class="ion-text-wrap" [class.chip]="mode == 'basic'">
class="ion-text-wrap"> <ion-icon name="fas-check" slot="start" aria-hidden="true"></ion-icon>
<ion-icon name="fas-check" slot="start" aria-hidden="true"></ion-icon> {{ 'core.course.completion_manual:done' | translate }}
{{ 'core.course.completion_manual:done' | translate }} <ion-icon *ngIf="completion?.offline" name="fas-sync" [attr.aria-label]="'core.course.manualcompletionnotsynced' | translate"
</ion-button> slot="end"></ion-icon>
</ng-container> </ion-button>
<ng-container *ngIf="!completion.state"> <ion-button *ngIf="!completion.state" color="dark" fill="outline" [attr.aria-label]="accessibleDescription"
<ion-button color="dark" fill="outline" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)" (click)="completionClicked($event)" class="ion-text-wrap" [class.chip]="mode == 'basic'">
class="ion-text-wrap"> {{ 'core.course.completion_manual:markdone' | translate }}
{{ 'core.course.completion_manual:markdone' | translate }} <ion-icon *ngIf="completion?.offline" name="fas-sync" [attr.aria-label]="'core.course.manualcompletionnotsynced' | translate"
</ion-button> slot="end"></ion-icon>
</ng-container> </ion-button>
</ng-container> </ng-container>
<ng-container *ngIf="!completion.istrackeduser"> <ng-container *ngIf="!completion.istrackeduser">
<ion-button disabled="true" color="dark" fill="outline" class="ion-text-wrap"> <ion-button disabled="true" color="dark" fill="outline" class="ion-text-wrap" [class.chip]="mode == 'basic'">
{{ 'core.course.completion_manual:markdone' | translate }} {{ 'core.course.completion_manual:markdone' | translate }}
</ion-button> </ion-button>
</ng-container> </ng-container>
</div> </ng-container>

View File

@ -13,8 +13,7 @@
// limitations under the License. // limitations under the License.
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core';
import { CoreCourseCompletionType } from '@features/course/services/course'; import { CoreCourseCompletionMode, CoreCourseCompletionType } from '@features/course/services/course';
import { CoreCourseHelper, CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; import { CoreCourseHelper, CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
import { CoreUser } from '@features/user/services/user'; import { CoreUser } from '@features/user/services/user';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
@ -31,6 +30,7 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan
@Input() completion?: CoreCourseModuleCompletionData; // The completion status. @Input() completion?: CoreCourseModuleCompletionData; // The completion status.
@Input() moduleName?: string; // The name of the module this completion affects. @Input() moduleName?: string; // The name of the module this completion affects.
@Input() mode: CoreCourseCompletionMode = CoreCourseCompletionMode.FULL; // Show full completion status or a basic mode.
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes. @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
accessibleDescription: string | null = null; accessibleDescription: string | null = null;
@ -97,6 +97,9 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan
return; return;
} }
event.stopPropagation();
event.preventDefault();
await CoreCourseHelper.changeManualCompletion(this.completion, event); await CoreCourseHelper.changeManualCompletion(this.completion, event);
// @deprecated MANUAL_COMPLETION_CHANGED is deprecated since 4.0 use COMPLETION_CHANGED instead. // @deprecated MANUAL_COMPLETION_CHANGED is deprecated since 4.0 use COMPLETION_CHANGED instead.

View File

@ -11,7 +11,7 @@
display: block; display: block;
bottom: 0; bottom: 0;
z-index: 3; z-index: 3;
box-shadow: 0px -3px 3px rgba(var(--drop-shadow)); border-top: 1px solid var(--stroke);
@include core-transition(all, 200ms); @include core-transition(all, 200ms);

View File

@ -1,9 +1,8 @@
<ion-card *ngIf="module.handlerData && module.visibleoncoursepage !== 0"> <ion-card *ngIf="module.handlerData && module.visibleoncoursepage !== 0">
<ng-container *ngIf="!module.handlerData.loading"> <ng-container *ngIf="!module.handlerData.loading">
<ion-item id="core-course-module-{{module.id}}" detail="false" lines="none" <ion-item id="core-course-module-{{module.id}}" detail="false"
class="ion-text-wrap core-course-module-handler core-module-main-item {{module.handlerData.class}}" class="ion-text-wrap core-course-module-handler core-module-main-item {{module.handlerData.class}}"
(click)="moduleClicked($event)" [attr.aria-label]="module.handlerData.a11yTitle" [ngClass]="{ (click)="moduleClicked($event)" [attr.aria-label]="module.handlerData.a11yTitle" [ngClass]="{
'has-module-info': hasInfo,
'item-media': module.handlerData.icon, 'item-media': module.handlerData.icon,
'item-dimmed': module.visible === 0 || module.uservisible === false 'item-dimmed': module.visible === 0 || module.uservisible === false
}" [button]="module.handlerData.action && module.uservisible"> }" [button]="module.handlerData.action && module.uservisible">
@ -18,26 +17,27 @@
[courseId]="module.course" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated"> [courseId]="module.course" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated">
</core-format-text> </core-format-text>
</p> </p>
<ion-chip class="completioninfo completion_incomplete" *ngIf="completionStatus === 0">
<ion-icon name="fas-edit" aria-hidden="true"></ion-icon>
<ion-label>
{{ 'core.course.todo' | translate }}
</ion-label>
</ion-chip>
<ion-chip class="completioninfo completion_complete" *ngIf="completionStatus === 1 || completionStatus === 2"
color="success">
<ion-icon name="fas-check" aria-hidden="true"></ion-icon>
<ion-label>{{'core.course.done' | translate }}</ion-label>
</ion-chip>
<ion-chip class="completioninfo completion_fail" *ngIf="completionStatus === 3" color="danger">
<ion-icon name="fas-times" aria-hidden="true"></ion-icon>
<ion-label>{{'core.course.failed' | translate }}</ion-label>
</ion-chip>
<ion-chip *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" <div class="core-module-additional-info">
class="ion-text-wrap ion-text-start" [outline]="true"> <!-- Basic module completion. -->
<ion-label><span [innerHTML]="module.handlerData.extraBadge"></span></ion-label> <core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata"
</ion-chip> [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions"
[showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)" mode="basic">
</core-course-module-completion>
<ion-chip *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor"
class="ion-text-wrap ion-text-start" [outline]="true">
<ion-label><span [innerHTML]="module.handlerData.extraBadge"></span></ion-label>
</ion-chip>
<!-- Hidden badges -->
<ion-badge color="warning" *ngIf="module.visible === 0 && (!section || section.visible)">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
<ion-badge color="warning" *ngIf="module.visible !== 0 && module.isStealth">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
</div>
</ion-label> </ion-label>
<!-- Buttons. --> <!-- Buttons. -->
<div slot="end" *ngIf="module.uservisible !== false" class="buttons core-module-buttons" <div slot="end" *ngIf="module.uservisible !== false" class="buttons core-module-buttons"
@ -58,61 +58,39 @@
</div> </div>
</div> </div>
</ion-item> </ion-item>
<ion-item *ngIf="hasInfo" id="core-course-module-{{module.id}}-info" detail="false" lines="none" <ion-item *ngIf="hasInfo" id="core-course-module-{{module.id}}-info" detail="false"
class="ion-text-wrap core-course-module-handler core-course-module-info {{module.handlerData.class}}" [ngClass]="{ class="ion-text-wrap core-course-module-handler core-course-module-info {{module.handlerData.class}}" [ngClass]="{
'item-media': module.handlerData.icon, 'item-media': module.handlerData.icon,
'item-dimmed': module.visible === 0 || module.uservisible === false 'item-dimmed': module.visible === 0 || module.uservisible === false
}"> }">
<ion-label> <ion-label collapsible-item>
<core-format-text class="core-module-description" *ngIf="module.description" [maxHeight]="80" [text]="module.description" <core-format-text class="core-module-description" *ngIf="module.description" [text]="module.description"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course"> contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
</core-format-text> </core-format-text>
<!-- Activity dates. --> <!-- Module completion. Only auto conditions-->
<div *ngIf="showActivityDates && module.dates && module.dates.length" class="core-module-dates">
<p *ngFor="let date of module.dates">
<ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp *
1000 | coreFormatDate:'strftimedatetime' }}
</p>
</div>
<!-- Module completion. -->
<core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata" <core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata"
[moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions" [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions">
[showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)">
</core-course-module-completion> </core-course-module-completion>
<div *ngIf="module.completiondata?.offline"> <div class="core-module-dates-availabilityinfo"
<ion-chip color="warning"> *ngIf="(showActivityDates && module.dates && module.dates.length) || module.availabilityinfo">
<ion-icon name="fas-sync" aria-hidden="true"></ion-icon> <!-- Activity dates. -->
<ion-label>{{ 'core.course.manualcompletionnotsynced' | translate }}</ion-label> <div *ngIf="showActivityDates && module.dates && module.dates.length" class="core-module-dates">
</ion-chip> <p *ngFor="let date of module.dates">
</div> <ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp
*
1000 | coreFormatDate:'strftimedatetime' }}
</p>
</div>
<!-- Availability --> <!-- Availability info -->
<div *ngIf="module.visible === 0 && (!section || section.visible)"> <div *ngIf="module.availabilityinfo" class="core-module-availabilityinfo">
<ion-chip>
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon>
<ion-label>{{ 'core.course.hiddenfromstudents' | translate }}</ion-label>
</ion-chip>
</div>
<div *ngIf="module.visible !== 0 && module.isStealth">
<ion-chip>
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon>
<ion-label>{{ 'core.course.hiddenoncoursepage' | translate }}</ion-label>
</ion-chip>
</div>
<div *ngIf="module.availabilityinfo">
<ion-chip class="core-module-availabilityinfo">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon> <ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
<ion-label> <core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
[courseId]="module.course"> </core-format-text>
</core-format-text> </div>
</ion-label>
</ion-chip>
</div> </div>
</ion-label> </ion-label>
@ -122,7 +100,7 @@
<!-- Loading. --> <!-- Loading. -->
<ion-item *ngIf="module.handlerData.loading" role="status" class="ion-text-wrap" id="core-course-module-{{module.id}}" <ion-item *ngIf="module.handlerData.loading" role="status" class="ion-text-wrap" id="core-course-module-{{module.id}}"
[attr.aria-label]="module.handlerData.a11yTitle" [attr.aria-label]="module.handlerData.a11yTitle"
[ngClass]="['core-course-module-handler', 'core-module-loading', module.handlerData.class]" detail="false" lines="none"> [ngClass]="['core-course-module-handler', 'core-module-loading', module.handlerData.class]" detail="false">
<ion-label> <ion-label>
<ion-spinner [attr.aria-label]="'core.loading' | translate"></ion-spinner> <ion-spinner [attr.aria-label]="'core.loading' | translate"></ion-spinner>
</ion-label> </ion-label>

View File

@ -1,12 +1,16 @@
@import "~theme/globals"; @import "~theme/globals";
:host { :host {
--horizontal-margin: 10px;
.item.core-module-main-item { ion-card {
--min-height: 52px; margin-left: var(--horizontal-margin);
margin-right: var(--horizontal-margin);
} }
.core-module-main-item { ion-item.core-module-main-item {
--min-height: 52px;
.core-module-buttons, .core-module-buttons,
.buttons.core-module-buttons { .buttons.core-module-buttons {
margin: 0; margin: 0;
@ -31,17 +35,29 @@
.core-module-buttons core-course-module-completion { .core-module-buttons core-course-module-completion {
text-align: center; text-align: center;
} }
.core-module-additional-info {
display: flex;
align-items: center;
}
} }
.core-course-module-info { .core-course-module-info {
ion-badge { .core-module-dates-availabilityinfo {
text-align: start; background: var(--light);
border-radius: var(--small-radius);
padding: 8px;
}
.core-module-dates + .core-module-availabilityinfo {
border-top: 1px solid var(--stroke);
padding-top: 8px;
} }
.core-module-availabilityinfo { .core-module-availabilityinfo {
font-size: 90%; font-size: 90%;
ul { ::ng-deep ul {
margin-block-start: 0.5em; margin-top: 0.5em;
} }
} }
} }
@ -57,10 +73,6 @@
margin-top: 0px; margin-top: 0px;
} }
.core-module-main-item.has-module-info {
--inner-border-width: 0px;
}
.core-module-availabilityinfo ion-icon, .core-module-availabilityinfo ion-icon,
.core-module-dates ion-icon { .core-module-dates ion-icon {
@include margin-horizontal(null, 8px); @include margin-horizontal(null, 8px);

View File

@ -20,7 +20,7 @@ import {
CoreCourseModuleCompletionData, CoreCourseModuleCompletionData,
CoreCourseSection, CoreCourseSection,
} from '@features/course/services/course-helper'; } from '@features/course/services/course-helper';
import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate'; import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
/** /**
@ -47,7 +47,6 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
hasInfo = false; hasInfo = false;
showLegacyCompletion = false; // Whether to show module completion in the old format. showLegacyCompletion = false; // Whether to show module completion in the old format.
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled. showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
completionStatus?: CoreCourseModuleCompletionStatus;
/** /**
* Component being initialized. * Component being initialized.
@ -62,20 +61,11 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
} }
this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title; this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title;
this.completionStatus = this.module.completiondata === undefined ||
!this.module.completiondata?.istrackeduser ||
this.module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: this.module.completiondata.state;
this.hasInfo = !!( this.hasInfo = !!(
this.module.description || this.module.description ||
(this.showActivityDates && this.module.dates && this.module.dates.length) || (this.showActivityDates && this.module.dates && this.module.dates.length) ||
(this.module.completiondata && (this.module.completiondata && this.showCompletionConditions && this.module.completiondata.isautomatic) ||
((this.showManualCompletion && !this.module.completiondata.isautomatic) ||
(this.showCompletionConditions && this.module.completiondata.isautomatic))
) ||
this.module.completiondata?.offline ||
(this.module.visible === 0 && (!this.section || this.section.visible)) || (this.module.visible === 0 && (!this.section || this.section.visible)) ||
(this.module.visible !== 0 && this.module.isStealth) || (this.module.visible !== 0 && this.module.isStealth) ||
(this.module.availabilityinfo) (this.module.availabilityinfo)

View File

@ -1,8 +1,5 @@
<core-navbar-buttons slot="end"> <core-navbar-buttons slot="end">
<core-context-menu> <core-context-menu>
<core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()"
iconAction="fas-info-circle">
</core-context-menu-item>
<core-context-menu-item *ngFor="let item of courseMenuHandlers" [priority]="item.priority" (action)="openMenuItem(item)" <core-context-menu-item *ngFor="let item of courseMenuHandlers" [priority]="item.priority" (action)="openMenuItem(item)"
[content]="item.data.title | translate" [iconAction]="item.data.icon" [class]="item.data.class"> [content]="item.data.title | translate" [iconAction]="item.data.icon" [class]="item.data.class">
</core-context-menu-item> </core-context-menu-item>

View File

@ -379,16 +379,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
} }
} }
/**
* Open the course summary
*/
openCourseSummary(): void {
CoreNavigator.navigateToSitePath(
`/course/${this.course.id}/preview`,
{ params: { course: this.course, avoidOpenCourse: true } },
);
}
/** /**
* Opens a menu item registered to the delegate. * Opens a menu item registered to the delegate.
* *

View File

@ -9,29 +9,23 @@
</h1> </h1>
</ion-title> </ion-title>
<ion-buttons slot="end"></ion-buttons> <ion-buttons slot="end">
<ion-button fill="clear" (click)="openCourseSummary()" [attr.aria-label]="'core.course.coursesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-item lines="full" class="core-format-progress-list ion-text-wrap collapsible-title"> <ion-item class="core-format-progress-list ion-text-wrap collapsible-title list-item-limited-width">
<ion-avatar slot="start" class="core-course-thumb" *ngIf="imageThumb"> <ion-avatar slot="start" class="core-course-thumb" *ngIf="imageThumb">
<img [src]="imageThumb" core-external-content alt="" /> <img [src]="imageThumb" core-external-content alt="" />
</ion-avatar> </ion-avatar>
<ion-label> <ion-label>
<ion-row> <p *ngIf="category">
<ion-col> <core-format-text [text]="category" contextLevel="coursecat" [contextInstanceId]="course!.categoryid">
<p *ngIf="category"> </core-format-text>
<core-format-text [text]="category" contextLevel="coursecat" [contextInstanceId]="course!.categoryid"> </p>
</core-format-text> <h1>{{ title }}</h1>
</p>
<h1>{{ title }}</h1>
</ion-col>
<ion-col size="auto" class="ion-align-self-center">
<ion-button fill="clear" (click)="openCourseSummary()" [attr.aria-label]="'core.course.coursesummary' | translate"
color="dark">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
<div class="core-course-progress" *ngIf="progress !== undefined"> <div class="core-course-progress" *ngIf="progress !== undefined">
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
</core-progress-bar> </core-progress-bar>

View File

@ -16,7 +16,7 @@
<core-empty-box *ngIf="!sections || !sections.length" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate"> <core-empty-box *ngIf="!sections || !sections.length" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
</core-empty-box> </core-empty-box>
<ion-list> <ion-list class="core-course-module-list-wrapper">
<ng-container *ngFor="let section of sections"> <ng-container *ngFor="let section of sections">
<ng-container *ngFor="let module of section.modules"> <ng-container *ngFor="let module of section.modules">
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"> <core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section">

View File

@ -27,36 +27,39 @@
<core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname" <core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname"
[componentId]="module.id" [expandDescription]="true"> [componentId]="module.id" [expandDescription]="true">
<div class="ion-padding" *ngIf="module.handlerData?.extraBadge"> <ion-item class="ion-text-wrap" *ngIf="module.handlerData?.extraBadge ||
<ion-chip class="ion-text-wrap ion-text-start" [color]="module.handlerData?.extraBadgeColor"> (module.visible === 0 && (!section || section.visible)) ||
<ion-label><span [innerHTML]="module.handlerData?.extraBadge"></span></ion-label> (module.visible !== 0 && module.isStealth) ||
</ion-chip> module.availabilityinfo">
</div>
<div class="ion-padding" *ngIf="module.visible === 0 && (!section || section.visible)">
<ion-chip class="ion-text-wrap">
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon>
<ion-label>{{ 'core.course.hiddenfromstudents' | translate }}</ion-label>
</ion-chip>
</div>
<div class="ion-padding" *ngIf="module.visible !== 0 && module.isStealth">
<ion-chip class="ion-text-wrap">
<ion-icon name="fas-eye-slash" aria-hidden="true"></ion-icon>
<ion-label>{{ 'core.course.hiddenoncoursepage' | translate }}</ion-label>
</ion-chip>
</div>
<div class="ion-padding core-module-availabilityinfo" *ngIf="module.availabilityinfo">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
<ion-label> <ion-label>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id" <div class="ion-padding" *ngIf="module.handlerData?.extraBadge">
[courseId]="courseId" class="ion-text-wrap"> <ion-chip *ngIf="module.handlerData?.extraBadge" [color]="module.handlerData?.extraBadgeColor"
</core-format-text> class="ion-text-wrap ion-text-start" [outline]="true">
<ion-label><span [innerHTML]="module.handlerData?.extraBadge"></span></ion-label>
</ion-chip>
</div>
<!-- Hidden badges -->
<div *ngIf="module.visible === 0 && (!section || section.visible)">
<ion-badge color="warning">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
</div>
<div *ngIf="module.visible !== 0 && module.isStealth">
<ion-badge color="warning">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
</div>
<!-- Availability info -->
<div *ngIf="module.availabilityinfo" class="core-module-availabilityinfo">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.course">
</core-format-text>
</div>
</ion-label> </ion-label>
</div> </ion-item>
<div class="ion-padding" *ngIf="module.completiondata?.offline">
<ion-chip color="warning" class="ion-text-wrap">
<ion-label>{{ 'core.course.manualcompletionnotsynced' | translate }}</ion-label>
</ion-chip>
</div>
<core-course-unsupported-module *ngIf="unsupported" [module]="module" [courseId]="courseId"></core-course-unsupported-module> <core-course-unsupported-module *ngIf="unsupported" [module]="module" [courseId]="courseId"></core-course-unsupported-module>
</core-course-module-info> </core-course-module-info>

View File

@ -27,6 +27,7 @@ import { CoreUtils } from '@services/utils/utils';
@Component({ @Component({
selector: 'page-core-course-module-preview', selector: 'page-core-course-module-preview',
templateUrl: 'module-preview.html', templateUrl: 'module-preview.html',
styleUrls: ['module-preview.scss'],
}) })
export class CoreCourseModulePreviewPage implements OnInit { export class CoreCourseModulePreviewPage implements OnInit {

View File

@ -0,0 +1,15 @@
@import "~theme/globals";
.core-module-availabilityinfo {
background: var(--light);
border-radius: var(--small-radius);
padding: 8px;
font-size: 90%;
::ng-deep ul {
margin-top: 0.5em;
}
ion-icon {
@include margin-horizontal(null, 8px);
}
}

View File

@ -76,6 +76,11 @@ export enum CoreCourseCompletionType {
AUTO = 1, AUTO = 1,
} }
export enum CoreCourseCompletionMode {
FULL = 'full',
BASIC = 'basic',
}
/** /**
* Completion tracking valid values. * Completion tracking valid values.
*/ */

View File

@ -12,8 +12,8 @@
</ng-container> </ng-container>
</div> </div>
<ion-item class="ion-text-wrap" button lines="none" detail="false" (click)="openCourse()" <ion-item class="ion-text-wrap" button detail="false" (click)="openCourse()" [attr.aria-label]="course.displayname || course.fullname"
[attr.aria-label]="course.displayname || course.fullname" [class.item-disabled]="course.visible == 0"> [class.item-disabled]="course.visible == 0">
<ng-container *ngIf="layout == 'list' || layout == 'listwithenrol'"> <ng-container *ngIf="layout == 'list' || layout == 'listwithenrol'">
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon core-course-thumb"> <ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon core-course-thumb">

View File

@ -101,6 +101,8 @@ ion-chip {
// List layout. // List layout.
ion-card.core-course-list-item { ion-card.core-course-list-item {
max-width: var(--list-item--max-width);
ion-icon.course-icon { ion-icon.course-icon {
padding: 12px; padding: 12px;
font-size: calc(var(--avatar-size) - 24px); font-size: calc(var(--avatar-size) - 24px);

View File

@ -160,7 +160,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
const tint = CoreColors.lighter(this.course.color, 50); const tint = CoreColors.lighter(this.course.color, 50);
this.element.style.setProperty('--course-color-tint', tint); this.element.style.setProperty('--course-color-tint', tint);
} else { } else if(this.course.colorNumber !== undefined) {
this.element.classList.add('course-color-' + this.course.colorNumber); this.element.classList.add('course-color-' + this.course.colorNumber);
} }
} }

View File

@ -3,8 +3,8 @@
[style.background-color]="course.color"> [style.background-color]="course.color">
<img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" /> <img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" />
</div> </div>
<ion-item button lines="none" (click)="openCourse()" [attr.aria-label]="course.displayname || course.fullname" <ion-item button (click)="openCourse()" [attr.aria-label]="course.displayname || course.fullname" class="core-course-header"
class="core-course-header" [class.item-disabled]="course.visible == 0" [class.item-disabled]="course.visible == 0"
[class.core-course-only-title]="!showAll || progress < 0 && completionUserTracked === false" detail="false"> [class.core-course-only-title]="!showAll || progress < 0 && completionUserTracked === false" detail="false">
<ion-label class="ion-text-wrap core-course-title" <ion-label class="ion-text-wrap core-course-title"
[class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)" [class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)"
@ -53,7 +53,7 @@
</ion-button> </ion-button>
</div> </div>
</ion-item> </ion-item>
<ion-item *ngIf="showAll && progress >= 0 && completionUserTracked !== false" lines="none" class="core-course-progress"> <ion-item *ngIf="showAll && progress >= 0 && completionUserTracked !== false" class="core-course-progress">
<ion-label> <ion-label>
<core-progress-bar [progress]="progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar> <core-progress-bar [progress]="progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
</ion-label> </ion-label>

View File

@ -25,53 +25,56 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="categoriesLoaded"> <core-loading [hideUntil]="categoriesLoaded">
<ion-item *ngIf="currentCategory" class="ion-text-wrap"> <ion-list class="list-item-limited-width">
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon> <ion-item *ngIf="currentCategory" class="ion-text-wrap">
<ion-label> <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
<p class="item-heading">
<core-format-text [text]="currentCategory.name" contextLevel="coursecat" [contextInstanceId]="currentCategory.id">
</core-format-text>
</p>
<p *ngIf="currentCategory.description">
<core-format-text [text]="currentCategory.description" [maxHeight]="120" contextLevel="coursecat"
[contextInstanceId]="currentCategory.id"></core-format-text>
</p>
</ion-label>
</ion-item>
<ng-container *ngIf="categories.length > 0">
<ion-item-divider>
<ion-label> <ion-label>
<h2>{{ 'core.courses.categories' | translate }}</h2> <p class="item-heading">
<core-format-text [text]="currentCategory.name" contextLevel="coursecat" [contextInstanceId]="currentCategory.id">
</core-format-text>
</p>
<p *ngIf="currentCategory.description">
<core-format-text [text]="currentCategory.description" [maxHeight]="120" contextLevel="coursecat"
[contextInstanceId]="currentCategory.id"></core-format-text>
</p>
</ion-label> </ion-label>
</ion-item-divider> </ion-item>
<section *ngFor="let category of categories">
<ion-item button class="ion-text-wrap" (click)="openCategory(category.id)" [attr.aria-label]="category.name" detail="true"> <ng-container *ngIf="categories.length > 0">
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon> <ion-item-divider>
<ion-label> <ion-label>
<h2> <h2 class="big">{{ 'core.courses.categories' | translate }}</h2>
<core-format-text [text]="category.name" contextLevel="coursecat" [contextInstanceId]="category.id">
</core-format-text>
</h2>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="!showOnlyEnrolled && category.coursecount > 0" color="light"> </ion-item-divider>
<span aria-hidden="true">{{ category.coursecount }}</span> <ion-card *ngFor="let category of categories">
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span> <ion-item button class="ion-text-wrap" (click)="openCategory(category.id)" [attr.aria-label]="category.name"
</ion-badge> detail="true">
</ion-item> <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
</section> <ion-label>
</ng-container> <h2>
<core-format-text [text]="category.name" contextLevel="coursecat" [contextInstanceId]="category.id">
</core-format-text>
</h2>
</ion-label>
<ion-badge slot="end" *ngIf="!showOnlyEnrolled && category.coursecount > 0" color="light">
<span aria-hidden="true">{{ category.coursecount }}</span>
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
</ion-badge>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="courses.length > 0"> <ng-container *ngIf="courses.length > 0">
<ion-item-divider> <ion-item-divider>
<ion-label> <ion-label>
<h2 *ngIf="!showOnlyEnrolled">{{ 'core.courses.courses' | translate }}</h2> <h2 *ngIf="!showOnlyEnrolled" class="big">{{ 'core.courses.courses' | translate }}</h2>
<h2 *ngIf="showOnlyEnrolled">{{ 'core.courses.mycourses' | translate }}</h2> <h2 *ngIf="showOnlyEnrolled" class="big">{{ 'core.courses.mycourses' | translate }}</h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled"> <core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
</core-courses-course-list-item> </core-courses-course-list-item>
</ng-container> </ng-container>
</ion-list>
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap" <core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
[message]="'core.courses.nocoursesyet' | translate"> [message]="'core.courses.nocoursesyet' | translate">
</core-empty-box> </core-empty-box>

View File

@ -9,7 +9,7 @@
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-list> <ion-list class="list-item-limited-width">
<ng-container *ngFor="let block of blocks"> <ng-container *ngFor="let block of blocks">
<core-block *ngIf="block.visible" [block]="block" contextLevel="user" [instanceId]="userId"></core-block> <core-block *ngIf="block.visible" [block]="block" contextLevel="user" [instanceId]="userId"></core-block>
</ng-container> </ng-container>

View File

@ -31,7 +31,6 @@ import { CoreBlockDelegate } from '@features/block/services/block-delegate';
@Component({ @Component({
selector: 'page-core-courses-dashboard', selector: 'page-core-courses-dashboard',
templateUrl: 'dashboard.html', templateUrl: 'dashboard.html',
styleUrls: ['dashboard.scss'],
}) })
export class CoreCoursesDashboardPage implements OnInit, OnDestroy { export class CoreCoursesDashboardPage implements OnInit, OnDestroy {

View File

@ -37,8 +37,10 @@
</ion-item-divider> </ion-item-divider>
</ng-container> </ng-container>
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled"> <ion-list class="list-item-limited-width">
</core-courses-course-list-item> <core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
</core-courses-course-list-item>
</ion-list>
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreCourses($event)" [error]="loadMoreError"> <core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreCourses($event)" [error]="loadMoreError">
</core-infinite-loading> </core-infinite-loading>

View File

@ -22,9 +22,9 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-item class="ion-text-wrap divider" lines="none"> <ion-item class="ion-text-wrap divider">
<ion-label> <ion-label>
<h2>{{ 'core.courses.mycourses' | translate }}</h2> <h2 class="big">{{ 'core.courses.mycourses' | translate }}</h2>
</ion-label> </ion-label>
<div slot="end" class="flex-row"> <div slot="end" class="flex-row">
<!-- Download all courses. --> <!-- Download all courses. -->

View File

@ -11,6 +11,11 @@
--internal-loading-inline-min-height: calc(100vh - var(--core-header-toolbar-height) - var(--bottom-tabs-size) - 2px); --internal-loading-inline-min-height: calc(100vh - var(--core-header-toolbar-height) - var(--bottom-tabs-size) - 2px);
} }
core-block ::ng-deep ion-card.addon-block-myoverview {
--border-width: 0;
--background: transparent;
}
@if ($core-dashboard-logo) { @if ($core-dashboard-logo) {
.in-toolbar h1 .core-header-logo { .in-toolbar h1 .core-header-logo {
max-height: calc(var(--core-header-toolbar-height) - 24px); max-height: calc(var(--core-header-toolbar-height) - 24px);

View File

@ -36,8 +36,8 @@
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan"> <td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
</td> </td>
<th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan"> <th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan">
<ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" <ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" name="fas-chevron-right"
[name]="row.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" class="expandable-status-icon"> class="expandable-status-icon" [class.expandable-status-icon-expanded]="row.expanded">
</ion-icon> </ion-icon>
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt"> <ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
</ion-icon> </ion-icon>

View File

@ -96,6 +96,11 @@
.expandable-status-icon { .expandable-status-icon {
font-size: 14px; font-size: 14px;
@include margin-horizontal(0, 2px); @include margin-horizontal(0, 2px);
@include core-transition(transform, 200ms);
&.expandable-status-icon-expanded {
transform: var(--rotate-expandable);
}
} }
} }

View File

@ -20,9 +20,9 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-list> <ion-list class="core-sitelist">
<ng-container *ngIf="accountsList.currentSite"> <ion-card *ngIf="accountsList.currentSite">
<ion-item-divider sticky="true"> <ion-item-divider sticky="true" class="core-sitelist-sitename">
<ion-label> <ion-label>
<h2> <h2>
<core-format-text [text]="accountsList.currentSite.siteName" clean="true" <core-format-text [text]="accountsList.currentSite.siteName" clean="true"
@ -34,7 +34,7 @@
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<ion-item detail="false" class="item-current"> <ion-item detail="false">
<ion-avatar slot="start"> <ion-avatar slot="start">
<img [src]="accountsList.currentSite.avatar" core-external-content [siteId]="accountsList.currentSite.id" <img [src]="accountsList.currentSite.avatar" core-external-content [siteId]="accountsList.currentSite.id"
alt="{{ 'core.pictureof' | translate:{$a: accountsList.currentSite.fullName} }}" alt="{{ 'core.pictureof' | translate:{$a: accountsList.currentSite.fullName} }}"
@ -47,10 +47,10 @@
</ion-item> </ion-item>
<ng-container *ngTemplateOutlet="siteList; context: {sites: accountsList.sameSite}"></ng-container> <ng-container *ngTemplateOutlet="siteList; context: {sites: accountsList.sameSite}"></ng-container>
</ng-container> </ion-card>
<ng-container *ngFor="let sites of accountsList.otherSites"> <ion-card *ngFor="let sites of accountsList.otherSites">
<ion-item-divider sticky="true" *ngIf="sites[0]"> <ion-item-divider sticky="true" *ngIf="sites[0]" class="core-sitelist-sitename">
<ion-label> <ion-label>
<h2> <h2>
<core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text> <core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text>
@ -60,7 +60,7 @@
</ion-item-divider> </ion-item-divider>
<ng-container *ngTemplateOutlet="siteList; context: {sites: sites}"></ng-container> <ng-container *ngTemplateOutlet="siteList; context: {sites: sites}"></ng-container>
</ng-container> </ion-card>
</ion-list> </ion-list>
</core-loading> </core-loading>

View File

@ -28,6 +28,7 @@ import { ModalController } from '@singletons';
@Component({ @Component({
selector: 'core-login-sites', selector: 'core-login-sites',
templateUrl: 'sites.html', templateUrl: 'sites.html',
styleUrls: ['../../sitelist.scss'],
animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE], animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE],
}) })
export class CoreLoginSitesComponent implements OnInit { export class CoreLoginSitesComponent implements OnInit {

View File

@ -18,7 +18,7 @@
<ion-content> <ion-content>
<ion-list> <ion-list>
<ng-container *ngIf="!changingPassword"> <ng-container *ngIf="!changingPassword">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'core.login.forcepasswordchangenotice' | translate }}</h2> <h2>{{ 'core.login.forcepasswordchangenotice' | translate }}</h2>
<p class="ion-padding-top">{{ 'core.login.changepasswordinstructions' | translate }}</p> <p class="ion-padding-top">{{ 'core.login.changepasswordinstructions' | translate }}</p>
@ -29,7 +29,7 @@
</ion-button> </ion-button>
</ng-container> </ng-container>
<ng-container *ngIf="changingPassword"> <ng-container *ngIf="changingPassword">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p>{{ 'core.login.changepasswordreconnectinstructions' | translate }}</p> <p>{{ 'core.login.changepasswordreconnectinstructions' | translate }}</p>
</ion-label> </ion-label>
@ -38,7 +38,7 @@
{{ 'core.login.reconnect' | translate }} {{ 'core.login.reconnect' | translate }}
</ion-button> </ion-button>
</ng-container> </ng-container>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p>{{ 'core.login.changepasswordlogoutinstructions' | translate }}</p> <p>{{ 'core.login.changepasswordlogoutinstructions' | translate }}</p>
</ion-label> </ion-label>

View File

@ -54,7 +54,7 @@
<ng-container *ngIf="showScanQR"> <ng-container *ngIf="showScanQR">
<div class="ion-text-center ion-padding">{{ 'core.login.or' | translate }}</div> <div class="ion-text-center ion-padding">{{ 'core.login.or' | translate }}</div>
<ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()"> <ion-button expand="block" color="light" class="ion-margin" (click)="showInstructionsAndScanQR()">
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon> <ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
{{ 'core.scanqr' | translate }} {{ 'core.scanqr' | translate }}
</ion-button> </ion-button>
@ -68,7 +68,7 @@
</ion-button> </ion-button>
<ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers"> <ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3> <h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
</ion-label> </ion-label>
@ -81,12 +81,12 @@
</ion-list> </ion-list>
<ion-list *ngIf="canSignup" class="ion-padding-top core-login-sign-up"> <ion-list *ngIf="canSignup" class="ion-padding-top core-login-sign-up">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'core.login.firsttime' | translate }}</h3> <h3 class="item-heading">{{ 'core.login.firsttime' | translate }}</h3>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none" *ngIf="authInstructions"> <ion-item class="ion-text-wrap" *ngIf="authInstructions">
<ion-label> <ion-label>
<p> <p>
<core-format-text [text]="authInstructions" [filter]="false"></core-format-text> <core-format-text [text]="authInstructions" [filter]="false"></core-format-text>

View File

@ -232,7 +232,7 @@
</h3> </h3>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'core.considereddigitalminor' | translate }}</h3> <h3 class="item-heading">{{ 'core.considereddigitalminor' | translate }}</h3>
<p>{{ 'core.digitalminor_desc' | translate }}</p> <p>{{ 'core.digitalminor_desc' | translate }}</p>

View File

@ -10,7 +10,7 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-list lines="none"> <ion-list>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label>{{ 'core.login.passwordforgotteninstructions2' | translate }}</ion-label> <ion-label>{{ 'core.login.passwordforgotteninstructions2' | translate }}</ion-label>
</ion-item> </ion-item>

View File

@ -65,7 +65,7 @@
<ng-container *ngIf="showScanQR"> <ng-container *ngIf="showScanQR">
<div class="ion-text-center ion-padding">{{ 'core.login.or' | translate }}</div> <div class="ion-text-center ion-padding">{{ 'core.login.or' | translate }}</div>
<ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()"> <ion-button expand="block" color="light" class="ion-margin" (click)="showInstructionsAndScanQR()">
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon> <ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
{{ 'core.scanqr' | translate }} {{ 'core.scanqr' | translate }}
</ion-button> </ion-button>
@ -80,7 +80,7 @@
<!-- Identity providers. --> <!-- Identity providers. -->
<ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers"> <ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3> <h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
</ion-label> </ion-label>

View File

@ -42,7 +42,7 @@
</ion-item> </ion-item>
<ion-list [class.hidden]="!hasSites && !enteredSiteUrl" class="core-login-site-list"> <ion-list [class.hidden]="!hasSites && !enteredSiteUrl" class="core-login-site-list">
<ion-item lines="none" class="core-login-site-list-title"> <ion-item class="core-login-site-list-title">
<ion-label> <ion-label>
<h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2> <h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2>
</ion-label> </ion-label>
@ -73,7 +73,7 @@
</div> </div>
</ng-container> </ng-container>
<ion-item *ngIf="siteSelector == 'url'" lines="none"> <ion-item *ngIf="siteSelector == 'url'">
<ion-label> <ion-label>
<ion-button expand="block" [disabled]="!siteForm.valid" class="ion-text-wrap" type="submit"> <ion-button expand="block" [disabled]="!siteForm.valid" class="ion-text-wrap" type="submit">
{{ 'core.login.connect' | translate }} {{ 'core.login.connect' | translate }}
@ -85,7 +85,7 @@
<ng-container *ngIf="fixedSites"> <ng-container *ngIf="fixedSites">
<!-- Pick the site from a list of fixed sites. --> <!-- Pick the site from a list of fixed sites. -->
<ion-list *ngIf="siteSelector == 'list'"> <ion-list *ngIf="siteSelector == 'list'">
<ion-item lines="none"> <ion-item>
<ion-label> <ion-label>
<h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2> <h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2>
</ion-label> </ion-label>
@ -101,8 +101,7 @@
<ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl"> <ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
<div class="ion-text-center ion-padding ion-margin-top">{{ 'core.login.or' | translate }}</div> <div class="ion-text-center ion-padding ion-margin-top">{{ 'core.login.or' | translate }}</div>
<ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()" <ion-button expand="block" color="light" class="ion-margin" (click)="showInstructionsAndScanQR()" aria-haspopup="dialog">
aria-haspopup="dialog">
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon> <ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
{{ 'core.scanqr' | translate }} {{ 'core.scanqr' | translate }}
</ion-button> </ion-button>

View File

@ -21,9 +21,9 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-list> <ion-list class="core-sitelist">
<ng-container *ngFor="let sites of accountsList.otherSites"> <ion-card *ngFor="let sites of accountsList.otherSites">
<ion-item-divider sticky="true" *ngIf="sites[0]"> <ion-item-divider sticky="true" *ngIf="sites[0]" class="core-sitelist-sitename">
<ion-label> <ion-label>
<h2> <h2>
<core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text> <core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text>
@ -50,7 +50,7 @@
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
</ion-item> </ion-item>
</ng-container> </ion-card>
</ion-list> </ion-list>
</core-loading> </core-loading>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end"> <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end">

View File

@ -27,6 +27,7 @@ import { CoreAnimations } from '@components/animations';
@Component({ @Component({
selector: 'page-core-login-sites', selector: 'page-core-login-sites',
templateUrl: 'sites.html', templateUrl: 'sites.html',
styleUrls: ['../../sitelist.scss'],
animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE], animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE],
}) })
export class CoreLoginSitesPage implements OnInit { export class CoreLoginSitesPage implements OnInit {

View File

@ -0,0 +1,10 @@
ion-list.core-sitelist {
.core-sitelist-sitename {
ion-label {
margin-top: 8px;
margin-bottom: 8px;
}
border-bottom: 1px solid var(--stroke);
}
}

View File

@ -6,12 +6,7 @@ core-user-avatar {
display: none; display: none;
} }
:host-context(ion-toolbar) core-user-avatar ::ng-deep img { :host-context(ion-toolbar) core-user-avatar ::ng-deep img,
padding: 2px !important;
border: 1px solid var(--color);
}
:host-context(ion-tab-bar) core-user-avatar ::ng-deep img { :host-context(ion-tab-bar) core-user-avatar ::ng-deep img {
padding: 2px !important; padding: 2px !important;
border: 1px solid var(--color);
} }

View File

@ -58,7 +58,7 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
CoreDomUtils.openSideModal<void>({ CoreDomUtils.openSideModal<void>({
component: CoreMainMenuUserMenuComponent, component: CoreMainMenuUserMenuComponent,
cssClass: 'core-modal-lateral-sm', cssClass: 'core-modal-lateral',
}); });
} }

View File

@ -35,7 +35,7 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded" lines="none"> <ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-label> <ion-label>
<ion-spinner [attr.aria-label]="'core.loading' | translate"></ion-spinner> <ion-spinner [attr.aria-label]="'core.loading' | translate"></ion-spinner>
</ion-label> </ion-label>
@ -43,7 +43,7 @@
<ion-item button *ngFor="let handler of handlers" class="ion-text-wrap" (click)="handlerClicked($event, handler)" <ion-item button *ngFor="let handler of handlers" class="ion-text-wrap" (click)="handlerClicked($event, handler)"
[ngClass]="['core-user-menu-handler', handler.class || '']" [hidden]="handler.hidden" [ngClass]="['core-user-menu-handler', handler.class || '']" [hidden]="handler.hidden"
[attr.aria-label]="handler.title | translate" detail="true" lines="none"> [attr.aria-label]="handler.title | translate" detail="true">
<ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start" aria-hidden="true"></ion-icon> <ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{ handler.title | translate }}</p> <p class="item-heading">{{ handler.title | translate }}</p>

View File

@ -5,7 +5,6 @@
ion-tab-bar { ion-tab-bar {
height: var(--menutabbar-size); height: var(--menutabbar-size);
box-shadow: 0px -3px 3px rgba(var(--drop-shadow));
} }
@if ($core-always-show-main-menu) { @if ($core-always-show-main-menu) {
@ -83,7 +82,6 @@
height: calc(100% - var(--ion-safe-area-top) - var(--ion-safe-area-bottom)); height: calc(100% - var(--ion-safe-area-top) - var(--ion-safe-area-bottom));
flex-direction: column; flex-direction: column;
@include border-end(var(--border)); @include border-end(var(--border));
box-shadow: 3px 0 3px rgba(var(--drop-shadow));
border-top: 0; border-top: 0;
justify-content: flex-start; justify-content: flex-start;

View File

@ -36,8 +36,7 @@
</ion-content> </ion-content>
<ion-footer class="ion-no-border"> <ion-footer class="ion-no-border">
<ion-item button class="ion-text-wrap ion-text-center core-about-deviceinfo" detail="false" (click)="openPage('deviceinfo')" <ion-item button class="ion-text-wrap ion-text-center core-about-deviceinfo" detail="false" (click)="openPage('deviceinfo')">
lines="none">
<ion-label> <ion-label>
<h2>{{ appName }} {{ versionName }}</h2> <h2>{{ appName }} {{ versionName }}</h2>
</ion-label> </ion-label>

View File

@ -36,7 +36,7 @@
</ion-segment> </ion-segment>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap core-settings-general-color-scheme" *ngIf="colorSchemes.length > 0" <ion-item class="ion-text-wrap core-settings-general-color-scheme" *ngIf="colorSchemes.length > 0"
[lines]="selectedScheme=='system' && isAndroid ? 'none' : 'inset'"> [lines]="selectedScheme=='system' && isAndroid ? 'none' : ''">
<ion-label> <ion-label>
<h2>{{ 'core.settings.colorscheme' | translate }}</h2> <h2>{{ 'core.settings.colorscheme' | translate }}</h2>
<p *ngIf="colorSchemeDisabled" class="text-danger">{{ 'core.settings.forcedsetting' | translate }}</p> <p *ngIf="colorSchemeDisabled" class="text-danger">{{ 'core.settings.forcedsetting' | translate }}</p>

View File

@ -8,7 +8,7 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="dataLoaded"> <core-loading [hideUntil]="dataLoaded">
<ion-list> <ion-list class="core-course-module-list-wrapper">
<!-- Site home main contents. --> <!-- Site home main contents. -->
<ng-container *ngIf="section && section.hasContent"> <ng-container *ngIf="section && section.hasContent">
<ion-item class="ion-text-wrap" *ngIf="section.summary"> <ion-item class="ion-text-wrap" *ngIf="section.summary">
@ -55,12 +55,14 @@
</ion-content> </ion-content>
<ng-template #allCourseList> <ng-template #allCourseList>
<ion-item button class="ion-text-wrap" (click)="openAvailableCourses()" lines="none"> <ion-card>
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon> <ion-item button class="ion-text-wrap" (click)="openAvailableCourses()">
<ion-label> <ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon>
<h2>{{ 'core.courses.availablecourses' | translate}}</h2> <ion-label>
</ion-label> <h2>{{ 'core.courses.availablecourses' | translate}}</h2>
</ion-item> </ion-label>
</ion-item>
</ion-card>
</ng-template> </ng-template>
<ng-template #news> <ng-template #news>
@ -69,29 +71,35 @@
</ng-template> </ng-template>
<ng-template #categories> <ng-template #categories>
<ion-item button class="ion-text-wrap" (click)="openCourseCategories()" lines="none"> <ion-card>
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon> <ion-item button class="ion-text-wrap" (click)="openCourseCategories()">
<ion-label> <ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
<h2>{{ 'core.courses.categories' | translate}}</h2> <ion-label>
</ion-label> <h2>{{ 'core.courses.categories' | translate}}</h2>
</ion-item> </ion-label>
</ion-item>
</ion-card>
</ng-template> </ng-template>
<ng-template #enrolledCourseList> <ng-template #enrolledCourseList>
<ion-item button class="ion-text-wrap" (click)="openMyCourses()" lines="none"> <ion-card>
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"> <ion-item button class="ion-text-wrap" (click)="openMyCourses()">
</ion-icon> <ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true">
<ion-label> </ion-icon>
<h2>{{ 'core.courses.mycourses' | translate}}</h2> <ion-label>
</ion-label> <h2>{{ 'core.courses.mycourses' | translate}}</h2>
</ion-item> </ion-label>
</ion-item>
</ion-card>
</ng-template> </ng-template>
<ng-template #courseSearch> <ng-template #courseSearch>
<ion-item button class="ion-text-wrap" (click)="openSearch()" lines="none"> <ion-card>
<ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon> <ion-item button class="ion-text-wrap" (click)="openSearch()">
<ion-label> <ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon>
<h2>{{ 'core.courses.searchcourses' | translate}}</h2> <ion-label>
</ion-label> <h2>{{ 'core.courses.searchcourses' | translate}}</h2>
</ion-item> </ion-label>
</ion-item>
</ion-card>
</ng-template> </ng-template>

View File

@ -4,14 +4,17 @@ ion-item ion-icon {
display: inline-block; display: inline-block;
border-radius: var(--module-icon-radius); border-radius: var(--module-icon-radius);
padding: 0.7rem; padding: 0.7rem;
background-color: $gray-100; background-color: var(--gray-100);
color: var(--gray-900);
line-height: var(--size); line-height: var(--size);
--margin-end: 1rem; --margin-end: 1rem;
@include margin-horizontal(null, var(--margin-end)); @include margin-horizontal(null, var(--margin-end));
} }
core-course-module.core-sitehome-news ::ng-deep ion-card { core-spacer ::ng-deep .item {
--border-width: 0; border-radius: var(--medium-radius);
margin: 0; --horizontal-margin: 10px;
padding: 0; margin-left: var(--horizontal-margin);
margin-right: var(--horizontal-margin);
width: auto;
} }

View File

@ -179,7 +179,7 @@ core-format-text {
position: absolute; position: absolute;
@include position(null, 10px, 10px, null); @include position(null, 10px, 10px, null);
color: var(--ion-text-color); color: var(--ion-text-color);
border-radius: var(--small-radius); border-radius: var(--huge-radius);
background-color: var(--core-format-text-viewer-icon-background); background-color: var(--core-format-text-viewer-icon-background);
display: flex; display: flex;

View File

@ -36,7 +36,7 @@ $background-color-dark-rgb: color-to-rgb-list($background-color-dark) !default;
$ion-item-background: $white !default; $ion-item-background: $white !default;
$ion-item-background-rgb: color-to-rgb-list($ion-item-background) !default; $ion-item-background-rgb: color-to-rgb-list($ion-item-background) !default;
$ion-item-background-dark: mix(#ffffff, #000000, 20%) !default; // #333333 $ion-item-background-dark: $gray-900 !default;
$ion-item-background-dark-rgb: color-to-rgb-list($ion-item-background-dark) !default; $ion-item-background-dark-rgb: color-to-rgb-list($ion-item-background-dark) !default;
$primary: $blue !default; $primary: $blue !default;

View File

@ -184,44 +184,6 @@ ion-app.ios ion-header ion-title {
white-space: normal !important; white-space: normal !important;
} }
ion-button core-format-text .core-format-text-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
line-height: 1.2;
}
ion-button > * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
ion-button.ion-text-wrap {
white-space: normal;
core-format-text .core-format-text-content {
white-space: normal;
display: contents;
}
& > * {
white-space: normal;
}
}
ion-button.button-outline {
--background: var(--contrast-background);
}
ion-button ion-spinner {
--color: inherit !important;
}
ion-button:not(.button-has-icon-only) > ion-icon {
min-width: 20px;
}
@each $color-name, $unused in $colors { @each $color-name, $unused in $colors {
.text-#{$color-name}, .text-#{$color-name},
p.text-#{$color-name} { p.text-#{$color-name} {
@ -293,10 +255,6 @@ ion-icon {
} }
} }
ion-button.button-small ion-icon.faicon[slot] {
font-size: 1.5em !important;
}
// Buttons. // Buttons.
ion-button, ion-button,
ion-fab-button, ion-fab-button,
@ -306,10 +264,58 @@ button,
min-width: var(--a11y-min-target-size); min-width: var(--a11y-min-target-size);
} }
ion-button.button-outline {
--border-width: 1px;
--background: var(--contrast-background);
}
ion-button.button-solid {
--box-shadow: none;
}
ion-button core-format-text .core-format-text-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
line-height: 1.2;
}
ion-button > * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
ion-button.ion-text-wrap {
white-space: normal;
core-format-text .core-format-text-content {
white-space: normal;
display: contents;
}
& > * {
white-space: normal;
}
}
ion-button ion-spinner {
--color: inherit !important;
}
ion-button:not(.button-has-icon-only):not(.button-small) > ion-icon {
min-width: 20px;
}
ion-button.button.button-clear.button-has-icon-only { ion-button.button.button-clear.button-has-icon-only {
--border-radius: var(--huge-radius); --border-radius: var(--huge-radius);
} }
ion-button.button.button-solid,
ion-button.button.button-outline {
--border-radius: var(--small-radius);
}
// Clear buttons will be black. // Clear buttons will be black.
ion-button.button-clear { ion-button.button-clear {
--primary: var(--primary); --primary: var(--primary);
@ -409,8 +415,9 @@ ion-alert {
} }
// Ionic list. // Ionic list.
ion-list.list-md { ion-list {
padding: 0; padding: 0 !important;
--ion-item-background: transparent;
} }
// Safe areas // Safe areas
@ -736,6 +743,14 @@ ion-card {
display: inline !important; display: inline !important;
} }
ion-list.core-course-module-list-wrapper,
.list-item-limited-width,
.core-course-module-list-wrapper {
max-width: var(--list-item--max-width);
margin-left: auto;
margin-right: auto;
}
ion-toolbar h1 img.core-bar-button-image, ion-toolbar h1 img.core-bar-button-image,
ion-toolbar h1 .core-bar-button-image img { ion-toolbar h1 .core-bar-button-image img {
padding: 4px; padding: 4px;
@ -861,12 +876,14 @@ ion-badge {
border-radius: var(--big-radius); border-radius: var(--big-radius);
} }
ion-chip { ion-chip,
ion-button.chip {
line-height: 1.1; line-height: 1.1;
font-size: 12px; font-size: 12px;
padding: 4px 8px;
min-height: 24px; min-height: 24px;
height: auto; text-transform: none;
margin: 4px;
font-weight: normal;
ion-icon { ion-icon {
font-size: 16px; font-size: 16px;
@ -874,6 +891,18 @@ ion-chip {
@include margin(0, 8px, 0, 0); @include margin(0, 8px, 0, 0);
} }
ion-label {
white-space: normal !important;
}
}
ion-chip {
line-height: 1.1;
font-size: 12px;
padding: 4px 8px;
min-height: 24px;
height: auto;
&.ion-color { &.ion-color {
background: var(--ion-color-tint); background: var(--ion-color-tint);
&.chip-outline { &.chip-outline {
@ -882,10 +911,6 @@ ion-chip {
color: var(--ion-color-base); color: var(--ion-color-base);
} }
} }
ion-label {
white-space: normal !important;
}
} }
ion-searchbar { ion-searchbar {
@ -1017,6 +1042,20 @@ audio.core-media-adapt-width {
width: 100%; width: 100%;
} }
ion-item {
--inner-border-width: 0px;
}
ion-item.item-lines-full {
--inner-border-width: 0px;
--border-width: 0 0 1px 0;
}
ion-item.item-lines-inset {
--inner-border-width: 1px;
--border-width: 0px;
}
// Fake item. // Fake item.
div.fake-ion-item { div.fake-ion-item {
position: relative; position: relative;
@ -1029,7 +1068,6 @@ div.fake-ion-item {
text-decoration: none; text-decoration: none;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
} }
html.md div.fake-ion-item { html.md div.fake-ion-item {
@ -1209,8 +1247,33 @@ ion-item.item-input ion-input.has-focus {
} }
} }
ion-item-divider { ion-item-divider.item,
ion-item.item.divider {
--inner-padding-end: 8px; --inner-padding-end: 8px;
min-height: var(--min-height);
border-bottom-width: var(--item-divider-border-width);
--border-width: var(--item-divider-border-width);
--inner-border-width: 0 0 var(--item-divider-border-width) 0;
ion-label h2,
ion-label p.item-heading {
font-size: var(--item-divider-font-size);
font-weight: 500;
line-height: 1.5;
}
ion-label h2.big {
font-size: var(--item-divider-font-size-big);
}
.expandable-status-icon {
font-size: 18px;
@include core-transition(transform, 200ms);
@include margin-horizontal(null, 16px);
&.expandable-status-icon-expanded {
transform: var(--rotate-expandable);
}
}
} }
// Change default outline. // Change default outline.
@ -1273,6 +1336,80 @@ ion-grid.core-no-grid > ion-row {
right: 0; right: 0;
} }
[collapsible-item] {
--collapsible-display-toggle: none;
--collapsible-toggle-background: var(--ion-item-background);
--collapsible-min-button-height: 44px;
.collapsible-toggle {
display: var(--collapsible-display-toggle);
}
&.collapsible-enabled {
--collapsible-display-toggle: block;
.collapsible-toggle {
display: var(--collapsible-display-toggle);
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
z-index: 7;
text-transform: none;
text-align: end;
font-size: 14px;
background-color: var(--collapsible-toggle-background);
color: var(--text-color);
margin: 0;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12);
.collapsible-toggle-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
position: absolute;
@include position(null, 0, 0, 0);
background: -webkit-linear-gradient(top, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
background: linear-gradient(to bottom, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
z-index: 6;
}
}
&.collapsible-expanded {
max-height: none !important;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
.collapsible-toggle-arrow {
transform: rotate(-90deg);
}
}
}
}
ion-header[collapsible] { ion-header[collapsible] {
@include core-transition(all, 500ms); @include core-transition(all, 500ms);

View File

@ -76,13 +76,13 @@
--core-progressbar-text-color: var(--gray-100); --core-progressbar-text-color: var(--gray-100);
--ion-item-background: #{$ion-item-background-dark}; --ion-item-background: #{$ion-item-background-dark};
--ion-item-detail-icon-color: var(--white); --item-divider-background: var(--ion-item-background);
--item-divider-background: var(--gray-800);
--item-divider-color: var(--text-color); --item-divider-color: var(--text-color);
--spacer-background: var(--gray-100); --spacer-background: var(--gray-700);
--core-combobox-background: var(--ion-item-background); --core-combobox-background: var(--ion-item-background);
--core-combobox-color: var(--white); --core-combobox-color: var(--gray-100);
--core-combobox-border-color: var(--stroke);
--core-login-background: var(--gray-900); --core-login-background: var(--gray-900);
--core-login-text-color: var(--white); --core-login-text-color: var(--white);

View File

@ -72,6 +72,8 @@
--module-icon-size: 24px; --module-icon-size: 24px;
--module-icon-radius: var(--medium-radius); --module-icon-radius: var(--medium-radius);
--list-item--max-width: 768px;
--ion-background-color: var(--background-color); --ion-background-color: var(--background-color);
--ion-background-color-rgb: #{$background-color-rgb}; --ion-background-color-rgb: #{$background-color-rgb};
@ -120,8 +122,8 @@
--core-header-toolbar-button-image-size: var(--a11y-min-target-size); --core-header-toolbar-button-image-size: var(--a11y-min-target-size);
--core-header-toolbar-background: var(--white); --core-header-toolbar-background: var(--white);
--core-header-toolbar-border-width: 3px; --core-header-toolbar-border-width: 1px;
--core-header-toolbar-border-color: var(--brand); --core-header-toolbar-border-color: var(--stroke);
--core-header-toolbar-color: var(--gray-900); --core-header-toolbar-color: var(--gray-900);
--core-header-toolbar-height: 56px; --core-header-toolbar-height: 56px;
html.ios { html.ios {
@ -145,6 +147,10 @@
} }
} }
ion-header::after {
display: none;
}
ion-header.header-ios ion-toolbar:last-of-type { ion-header.header-ios ion-toolbar:last-of-type {
--border-width: 0 0 var(--core-header-toolbar-border-width) 0; --border-width: 0 0 var(--core-header-toolbar-border-width) 0;
} }
@ -221,20 +227,18 @@
} }
--item-divider-min-height: calc(var(--a11y-min-target-size) + 8px); --item-divider-min-height: calc(var(--a11y-min-target-size) + 8px);
--item-divider-background: var(--light); --item-divider-background: var(--ion-item-background);
--item-divider-color: var(--text-color); --item-divider-color: var(--text-color);
--item-divider-border-width: 0px;
--item-divider-font-size: 16px;
--item-divider-font-size-big: 20px;
ion-item-divider, ion-item.divider { ion-item-divider, ion-item.divider {
--background: var(--item-divider-background); --background: var(--item-divider-background);
--color: var(--item-divider-color); --color: var(--item-divider-color);
--min-height: var(--item-divider-min-height); --min-height: var(--item-divider-min-height);
min-height: var(--min-height);
.expandable-status-icon {
font-size: 18px;
}
} }
--spacer-background: var(--item-divider-background); --spacer-background: var(--light);
core-spacer { core-spacer {
--item-divider-background: var(--spacer-background); --item-divider-background: var(--spacer-background);
} }
@ -298,9 +302,6 @@
--drop-shadow: 0, 0, 0, 0.18; --drop-shadow: 0, 0, 0, 0.18;
--core-menu-box-shadow-end: -4px 0px 16px rgba(var(--drop-shadow));
--core-menu-box-shadow-start: 4px 0px 16px rgba(var(--drop-shadow));
--core-question-correct-color: var(--success-shade); --core-question-correct-color: var(--success-shade);
--core-question-correct-color-bg: var(--success-tint); --core-question-correct-color-bg: var(--success-tint);
--core-question-incorrect-color: var(--danger); --core-question-incorrect-color: var(--danger);
@ -322,6 +323,11 @@
--core-dd-question-radius: 10px; --core-dd-question-radius: 10px;
--core-dd-question-border: var(--medium); --core-dd-question-border: var(--medium);
--rotate-expandable: rotate(90deg);
&[dir=rtl] {
--rotate-expandable: rotate(-90deg);
}
@for $i from 0 to length($core-course-image-background) { @for $i from 0 to length($core-course-image-background) {
--core-course-color-#{$i}: #{nth($core-course-image-background, $i + 1)}; --core-course-color-#{$i}: #{nth($core-course-image-background, $i + 1)};
--core-course-color-#{$i}-tint: #{get-color-tint(nth($core-course-image-background, $i + 1))}; --core-course-color-#{$i}-tint: #{get-color-tint(nth($core-course-image-background, $i + 1))};