commit
4fddde1a00
|
@ -48,7 +48,7 @@
|
||||||
"nl": "Nederlands",
|
"nl": "Nederlands",
|
||||||
"no": "Norsk",
|
"no": "Norsk",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"ps": "پښتو",
|
"ps": "لیسي",
|
||||||
"pt": "Português - Portugal",
|
"pt": "Português - Portugal",
|
||||||
"pt-br": "Português - Brasil",
|
"pt-br": "Português - Brasil",
|
||||||
"ro": "Română",
|
"ro": "Română",
|
||||||
|
|
|
@ -597,6 +597,8 @@
|
||||||
"addon.mod_data.timeadded": "data",
|
"addon.mod_data.timeadded": "data",
|
||||||
"addon.mod_data.timemodified": "data",
|
"addon.mod_data.timemodified": "data",
|
||||||
"addon.mod_data.usedate": "data",
|
"addon.mod_data.usedate": "data",
|
||||||
|
"addon.mod_data_fields_file.fieldtypelabel": "datafield_file",
|
||||||
|
"addon.mod_data_fields_picture.fieldtypelabel": "datafield_picture",
|
||||||
"addon.mod_feedback.analysis": "feedback",
|
"addon.mod_feedback.analysis": "feedback",
|
||||||
"addon.mod_feedback.anonymous": "feedback",
|
"addon.mod_feedback.anonymous": "feedback",
|
||||||
"addon.mod_feedback.anonymous_entries": "feedback",
|
"addon.mod_feedback.anonymous_entries": "feedback",
|
||||||
|
@ -1812,6 +1814,7 @@
|
||||||
"core.filenameexist": "local_moodlemobileapp",
|
"core.filenameexist": "local_moodlemobileapp",
|
||||||
"core.filenotfound": "resource",
|
"core.filenotfound": "resource",
|
||||||
"core.fileuploader.addfiletext": "repository",
|
"core.fileuploader.addfiletext": "repository",
|
||||||
|
"core.fileuploader.attachedfiles": "repository",
|
||||||
"core.fileuploader.audio": "local_moodlemobileapp",
|
"core.fileuploader.audio": "local_moodlemobileapp",
|
||||||
"core.fileuploader.audiotitle": "tiny_recordrtc",
|
"core.fileuploader.audiotitle": "tiny_recordrtc",
|
||||||
"core.fileuploader.camera": "local_moodlemobileapp",
|
"core.fileuploader.camera": "local_moodlemobileapp",
|
||||||
|
@ -1834,6 +1837,7 @@
|
||||||
"core.fileuploader.microphonepermissiondenied": "local_moodlemobileapp",
|
"core.fileuploader.microphonepermissiondenied": "local_moodlemobileapp",
|
||||||
"core.fileuploader.microphonepermissionrestricted": "local_moodlemobileapp",
|
"core.fileuploader.microphonepermissionrestricted": "local_moodlemobileapp",
|
||||||
"core.fileuploader.more": "data",
|
"core.fileuploader.more": "data",
|
||||||
|
"core.fileuploader.nofilesattached": "repository",
|
||||||
"core.fileuploader.pauserecording": "local_moodlemobileapp",
|
"core.fileuploader.pauserecording": "local_moodlemobileapp",
|
||||||
"core.fileuploader.photoalbums": "local_moodlemobileapp",
|
"core.fileuploader.photoalbums": "local_moodlemobileapp",
|
||||||
"core.fileuploader.readingfile": "local_moodlemobileapp",
|
"core.fileuploader.readingfile": "local_moodlemobileapp",
|
||||||
|
|
|
@ -14,9 +14,7 @@
|
||||||
<form [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-input labelPlacement="stacked" formControlName="subject" type="text"
|
<ion-input labelPlacement="stacked" formControlName="subject" type="text"
|
||||||
[placeholder]="'addon.blog.entrytitle' | translate" name="title">
|
[placeholder]="'addon.blog.entrytitle' | translate" name="title" [label]="'addon.blog.entrytitle' | translate" />
|
||||||
<p>{{ 'addon.blog.entrytitle' | translate }}</p>
|
|
||||||
</ion-input>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
@ -27,7 +25,7 @@
|
||||||
[elementId]="entry?.id ?? 'new_entry'" />
|
[elementId]="entry?.id ?? 'new_entry'" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item lines="none">
|
||||||
<core-combobox name="addon_blog_publish_to" formControlName="publishState" [label]="'addon.blog.publishto' | translate">
|
<core-combobox name="addon_blog_publish_to" formControlName="publishState" [label]="'addon.blog.publishto' | translate">
|
||||||
<ion-select-option class="core-select-option-title" [value]="publishState.draft">
|
<ion-select-option class="core-select-option-title" [value]="publishState.draft">
|
||||||
{{ 'addon.blog.publishtonoone' | translate }}
|
{{ 'addon.blog.publishtonoone' | translate }}
|
||||||
|
@ -54,7 +52,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div id="addon-blog-associations">
|
<div id="addon-blog-associations">
|
||||||
@if (associationsExpanded) {
|
@if (associationsExpanded) {
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item lines="none">
|
||||||
@if (associatedModule) {
|
@if (associatedModule) {
|
||||||
<ion-toggle formControlName="associateWithModule">
|
<ion-toggle formControlName="associateWithModule">
|
||||||
<core-format-text [text]="'addon.blog.associatewithmodule' | translate: {
|
<core-format-text [text]="'addon.blog.associatewithmodule' | translate: {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
@if (showMyEntriesToggle) {
|
@if (showMyEntriesToggle) {
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item lines="none" class="ion-text-wrap">
|
||||||
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">
|
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">
|
||||||
{{ 'addon.blog.showonlyyourentries' | translate }}
|
{{ 'addon.blog.showonlyyourentries' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content [fullscreen]="true">
|
<ion-content [fullscreen]="true">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item *ngFor="let type of types" class="addon-calendar-event ion-no-validation" [ngClass]="['addon-calendar-eventtype-'+type]">
|
<ion-item *ngFor="let type of types" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+type]" lines="none">
|
||||||
<ion-icon [name]="typeIcons[type]" slot="start" aria-hidden="true" />
|
<ion-icon [name]="typeIcons[type]" slot="start" aria-hidden="true" />
|
||||||
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()">
|
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()">
|
||||||
{{ 'addon.calendar.' + type + 'events' | translate}}
|
{{ 'addon.calendar.' + type + 'events' | translate}}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
@use "theme/globals" as *;
|
@use "theme/globals" as *;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
ion-item {
|
.item {
|
||||||
ion-icon, ion-radio {
|
ion-radio {
|
||||||
@include margin-horizontal(null, 8px);
|
@include margin-horizontal(null, 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
> ion-icon {
|
&.addon-calendar-event > ion-icon {
|
||||||
padding: 4px;
|
--margin-vertical: 8px;
|
||||||
font-size: var(--mdl-typography-icon-fontSize-md);
|
--margin-end: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: var(--mdl-typography-icon-fontSize-sm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngFor="let device of platform.devices"
|
<ion-item class="ion-text-wrap" *ngFor="let device of platform.devices" [class.item-current]="device.current"
|
||||||
[class.item-current]="device.current">
|
lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading" id="device-{{device.id}}">
|
<p class="item-heading" id="device-{{device.id}}">
|
||||||
<strong>{{ device.name }} {{ device.model }}</strong> ({{platform.platform}} {{ device.version }})
|
<strong>{{ device.name }} {{ device.model }}</strong> ({{platform.platform}} {{ device.version }})
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<h2>{{ 'core.settings.general' | translate }}</h2>
|
<h2>{{ 'core.settings.general' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="sendOnEnter" (ngModelChange)="sendOnEnterChanged()">
|
<ion-toggle [(ngModel)]="sendOnEnter" (ngModelChange)="sendOnEnterChanged()">
|
||||||
{{ 'addon.messages.useentertosend' | translate }}
|
{{ 'addon.messages.useentertosend' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
<!-- Contactable privacy. -->
|
<!-- Contactable privacy. -->
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-item *ngIf="!advancedContactable" class="ion-text-wrap ion-no-validation">
|
<ion-item *ngIf="!advancedContactable" class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="contactablePrivacy" (ngModelChange)="saveContactablePrivacy(contactablePrivacy)">
|
<ion-toggle [(ngModel)]="contactablePrivacy" (ngModelChange)="saveContactablePrivacy(contactablePrivacy)">
|
||||||
{{ 'addon.messages.blocknoncontacts' | translate }}
|
{{ 'addon.messages.blocknoncontacts' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<!-- If notifications not disabled, show toggles.
|
<!-- If notifications not disabled, show toggles.
|
||||||
If notifications are disabled, show "Disabled" instead of toggle. -->
|
If notifications are disabled, show "Disabled" instead of toggle. -->
|
||||||
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" class="ion-text-wrap ion-no-validation">
|
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" class="ion-text-wrap" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p>{{ 'core.settings.' + state | translate }}</p>
|
<p>{{ 'core.settings.' + state | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ng-container *ngFor="let processor of notification.processors">
|
<ng-container *ngFor="let processor of notification.processors">
|
||||||
<!-- If group messaging is enabled, display a simplified view. -->
|
<!-- If group messaging is enabled, display a simplified view. -->
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p>{{ processor.displayname }}</p>
|
<p>{{ processor.displayname }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!--- Apply grade to all team members. -->
|
<!--- Apply grade to all team members. -->
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngIf="assign!.teamsubmission && canSaveGrades">
|
<ion-item class="ion-text-wrap" *ngIf="assign!.teamsubmission && canSaveGrades" lines="none">
|
||||||
<ion-toggle [(ngModel)]="grade.applyToAll">
|
<ion-toggle [(ngModel)]="grade.applyToAll">
|
||||||
<p class="item-heading">{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</p>
|
<p class="item-heading">{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</p>
|
||||||
<p>{{ 'addon.mod_assign.applytoteam' | translate }}</p>
|
<p>{{ 'addon.mod_assign.applytoteam' | translate }}</p>
|
||||||
|
@ -371,7 +371,7 @@
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="canSaveGrades && allowAddAttempt" class="ion-no-validation">
|
<ion-item *ngIf="canSaveGrades && allowAddAttempt" lines="none">
|
||||||
<ion-toggle [(ngModel)]="grade.addAttempt">
|
<ion-toggle [(ngModel)]="grade.addAttempt">
|
||||||
<p>{{ 'addon.mod_assign.addattempt' | translate }}</p>
|
<p>{{ 'addon.mod_assign.addattempt' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
|
|
@ -50,12 +50,12 @@
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="submission.statusTranslated">
|
<p *ngIf="submission.statusTranslated">
|
||||||
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.statusColor">
|
<ion-badge class="ion-text-start ion-text-wrap" [color]="submission.statusColor">
|
||||||
{{ submission.statusTranslated }}
|
{{ submission.statusTranslated }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="submission.gradingStatusTranslationId">
|
<p *ngIf="submission.gradingStatusTranslationId">
|
||||||
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.gradingColor">
|
<ion-badge class="ion-text-start ion-text-wrap" [color]="submission.gradingColor">
|
||||||
{{ submission.gradingStatusTranslationId | translate }}
|
{{ submission.gradingStatusTranslationId | translate }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -41,7 +41,6 @@ import {
|
||||||
} from '../../services/book';
|
} from '../../services/book';
|
||||||
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||||
import { CoreUrlUtils } from '@services/utils/url';
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
import { IonicSlides } from '@ionic/angular';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a book contents.
|
* Page that displays a book contents.
|
||||||
|
@ -65,7 +64,6 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy {
|
||||||
displayNavBar = true;
|
displayNavBar = true;
|
||||||
navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
|
navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
|
||||||
swiperOpts: CoreSwipeSlidesOptions = {
|
swiperOpts: CoreSwipeSlidesOptions = {
|
||||||
modules: [IonicSlides],
|
|
||||||
autoHeight: true,
|
autoHeight: true,
|
||||||
observer: true,
|
observer: true,
|
||||||
observeParents: true,
|
observeParents: true,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<core-loading [hideUntil]="sessions.loaded">
|
<core-loading [hideUntil]="sessions.loaded">
|
||||||
<core-group-selector [groupInfo]="groupInfo" [(selected)]="groupId" (selectedChange)="reloadSessions()" [courseId]="courseId" />
|
<core-group-selector [groupInfo]="groupInfo" [(selected)]="groupId" (selectedChange)="reloadSessions()" [courseId]="courseId" />
|
||||||
|
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="showAll" (ionChange)="reloadSessions()">
|
<ion-toggle [(ngModel)]="showAll" (ionChange)="reloadSessions()">
|
||||||
{{ 'addon.mod_chat.showincompletesessions' | translate }}
|
{{ 'addon.mod_chat.showincompletesessions' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item lines="full" class="ion-text-wrap">
|
||||||
<ion-toggle [(ngModel)]="search.searchingAdvanced">
|
<ion-toggle [(ngModel)]="search.searchingAdvanced">
|
||||||
{{ 'addon.mod_data.advancedsearch' | translate }}
|
<p class="item-heading">{{ 'addon.mod_data.advancedsearch' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm" #searchFormEl>
|
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm" #searchFormEl>
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
// Edit and search modal.
|
// Edit and search modal.
|
||||||
:host {
|
:host {
|
||||||
--input-border-color: var(--stroke);
|
|
||||||
--input-border-width: 1px;
|
|
||||||
--select-border-width: 0px;
|
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
table {
|
table {
|
||||||
|
@ -13,16 +10,6 @@
|
||||||
td {
|
td {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addon-data-latlong {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.input-units {
|
|
||||||
flex-grow: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.addon-data-advanced-search {
|
.addon-data-advanced-search {
|
||||||
|
@ -35,66 +22,15 @@
|
||||||
background-color: var(--ion-item-background);
|
background-color: var(--ion-item-background);
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
|
.has-errors {
|
||||||
ion-input {
|
.input-highlight,
|
||||||
border-bottom: var(--input-border-width) solid var(--input-border-color);
|
.select-highlight,
|
||||||
&.has-focus,
|
.textarea-highlight {
|
||||||
&.has-focus.ion-valid,
|
transform: scale(1);
|
||||||
&.ion-touched.ion-invalid {
|
|
||||||
--input-border-width: 2px;
|
|
||||||
}
|
}
|
||||||
|
core-rich-text-editor.ion-touched.ng-invalid {
|
||||||
&.has-focus {
|
--stroke: var(--danger);
|
||||||
--input-border-color: var(--primary);
|
|
||||||
}
|
}
|
||||||
&.has-focus.ion-valid {
|
|
||||||
--input-border-color: var(--success);
|
|
||||||
}
|
|
||||||
&.ion-touched.ion-invalid {
|
|
||||||
--input-border-color: var(--danger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core-rich-text-editor {
|
|
||||||
border-bottom: var(--select-border-width) solid var(--input-border-color);
|
|
||||||
|
|
||||||
&.ion-touched.ng-valid,
|
|
||||||
&.ion-touched.ng-invalid {
|
|
||||||
--select-border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ion-touched.ng-valid {
|
|
||||||
--input-border-color: var(--success);
|
|
||||||
}
|
|
||||||
&.ion-touched.ng-invalid {
|
|
||||||
--input-border-color: var(--danger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ion-select {
|
|
||||||
border-bottom: var(--select-border-width) solid var(--input-border-color);
|
|
||||||
|
|
||||||
&.ion-touched.ion-valid,
|
|
||||||
&.ion-touched.ion-invalid {
|
|
||||||
--select-border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ion-touched.ion-valid {
|
|
||||||
--input-border-color: var(--success);
|
|
||||||
}
|
|
||||||
&.ion-touched.ion-invalid {
|
|
||||||
--input-border-color: var(--danger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-errors ion-input.ion-invalid {
|
|
||||||
--input-border-width: 2px;
|
|
||||||
--input-border-color: var(--danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-errors ion-select.ion-invalid,
|
|
||||||
.has-errors core-rich-text-editor.ng-invalid {
|
|
||||||
--select-border-width: 2px;
|
|
||||||
--input-border-color: var(--danger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-mark-required {
|
.core-mark-required {
|
||||||
|
|
|
@ -55,7 +55,7 @@ $grid-column-paddings: (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not let block elements to define widths or heights.
|
// Do not let block elements to define widths or heights.
|
||||||
address, article, aside, blockquote, canvas, dd, div, dl, dt, fieldset, figcaption, figure, footer, form,
|
address, article, aside, blockquote, canvas, dd, dl, dt, fieldset, figcaption, figure, footer, form,
|
||||||
h1, h2, h3, h4, h5, h6,
|
h1, h2, h3, h4, h5, h6,
|
||||||
header, li, main, nav, noscript, ol, p, pre, section, table, tfoot, ul, video {
|
header, li, main, nav, noscript, ol, p, pre, section, table, tfoot, ul, video {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<span *ngIf="editMode && form">
|
<ng-container *ngIf="editMode && form">
|
||||||
<span [core-mark-required]="field.required" class="core-mark-required"></span>
|
|
||||||
<core-attachments [files]="files" [maxSize]="maxSizeBytes" maxSubmissions="1" [component]="component" [componentId]="componentId"
|
<core-attachments [files]="files" [maxSize]="maxSizeBytes" maxSubmissions="1" [component]="component" [componentId]="componentId"
|
||||||
[allowOffline]="true" [courseId]="database?.course" />
|
[allowOffline]="true" [courseId]="database?.course" [required]="field.required"
|
||||||
|
[title]="'addon.mod_data_fields_file.fieldtypelabel' | translate" />
|
||||||
<core-input-errors *ngIf="error" [errorText]="error" />
|
<core-input-errors *ngIf="error" [errorText]="error" />
|
||||||
</span>
|
</ng-container>
|
||||||
|
|
||||||
<span *ngIf="searchMode && form" [formGroup]="form">
|
<ng-container *ngIf="searchMode && form" [formGroup]="form">
|
||||||
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name" />
|
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name" />
|
||||||
</span>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="displayMode">
|
<ng-container *ngIf="displayMode">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"fieldtypelabel": "File"
|
||||||
|
}
|
|
@ -3,13 +3,15 @@
|
||||||
|
|
||||||
<ng-container *ngIf="editMode">
|
<ng-container *ngIf="editMode">
|
||||||
<span [core-mark-required]="field.required" class="core-mark-required"></span>
|
<span [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||||
<div class="addon-data-latlong flex-row">
|
<div class="addon-data-latlong">
|
||||||
<ion-input type="text" [formControlName]="'f_'+field.id+'_0'" maxlength="10" />
|
<ion-input type="text" [formControlName]="'f_'+field.id+'_0'" maxlength="10" placeholder="0.0">
|
||||||
<div class="input-units">°N</div>
|
<div class="input-units" slot="end">°N</div>
|
||||||
|
</ion-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="addon-data-latlong flex-row">
|
<div class="addon-data-latlong">
|
||||||
<ion-input type="text" [formControlName]="'f_'+field.id+'_1'" maxlength="10" />
|
<ion-input type="text" [formControlName]="'f_'+field.id+'_1'" maxlength="10" placeholder="0.0">
|
||||||
<div class="input-units">°E</div>
|
<div class="input-units" slot="end">°E</div>
|
||||||
|
</ion-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="addon-data-latlong" *ngIf="locationServicesEnabled">
|
<div class="addon-data-latlong" *ngIf="locationServicesEnabled">
|
||||||
<ion-button (click)="getLocation($event)">
|
<ion-button (click)="getLocation($event)">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span>
|
<span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||||
<ion-select [formControlName]="'f_'+field.id" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
<ion-select [formControlName]="'f_'+field.id" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
||||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: field.name}" interface="action-sheet">
|
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: field.name}" interface="action-sheet">
|
||||||
<ion-select-option value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
<ion-select-option *ngIf="!editMode || !field.required" value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
||||||
<ion-select-option *ngFor="let option of options" [value]="option">
|
<ion-select-option *ngFor="let option of options" [value]="option">
|
||||||
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||||
|
|
|
@ -35,6 +35,7 @@ export class AddonModDataFieldMenuComponent extends AddonModDataFieldPluginBaseC
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options = this.field.param1.split('\n');
|
this.options = this.field.param1.split('\n');
|
||||||
|
this.options = this.options.filter((option) => option !== '');
|
||||||
|
|
||||||
let val: string | undefined;
|
let val: string | undefined;
|
||||||
if (this.editMode && this.value) {
|
if (this.editMode && this.value) {
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<span *ngIf="editMode && form" [formGroup]="form">
|
<ng-container *ngIf="editMode && form" [formGroup]="form">
|
||||||
<span [core-mark-required]="field.required" class="core-mark-required"></span>
|
|
||||||
<core-attachments [files]="files" [maxSize]="maxSizeBytes" maxSubmissions="1" [component]="component" [componentId]="componentId"
|
<core-attachments [files]="files" [maxSize]="maxSizeBytes" maxSubmissions="1" [component]="component" [componentId]="componentId"
|
||||||
[allowOffline]="true" acceptedTypes="image" [courseId]="database?.course" />
|
[allowOffline]="true" acceptedTypes="image" [courseId]="database?.course" [required]="field.required"
|
||||||
|
[title]="'addon.mod_data_fields_picture.fieldtypelabel' | translate" />
|
||||||
<core-input-errors *ngIf="error" [errorText]="error" />
|
<core-input-errors *ngIf="error" [errorText]="error" />
|
||||||
|
|
||||||
<ion-input [label]="'addon.mod_data.alttext' | translate" labelPlacement="stacked" type="text"
|
<ion-input [label]="'addon.mod_data.alttext' | translate" labelPlacement="stacked" type="text"
|
||||||
[formControlName]="'f_'+field.id+'_alttext'" [placeholder]=" 'addon.mod_data.alttext' | translate" />
|
[formControlName]="'f_'+field.id+'_alttext'" [placeholder]=" 'addon.mod_data.alttext' | translate" />
|
||||||
</span>
|
</ng-container>
|
||||||
|
|
||||||
<span *ngIf="searchMode && form" [formGroup]="form">
|
<ng-container *ngIf="searchMode && form" [formGroup]="form">
|
||||||
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name" />
|
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name" />
|
||||||
</span>
|
</ng-container>
|
||||||
|
|
||||||
<button class="as-link" *ngIf="listMode && imageUrl" (click)="navigateEntry()">
|
<button class="as-link" *ngIf="listMode && imageUrl" (click)="navigateEntry()">
|
||||||
<img [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture" core-external-content />
|
<img [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture" core-external-content />
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"fieldtypelabel": "Image"
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
<ion-select [formControlName]="'f_'+field.id" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
<ion-select [formControlName]="'f_'+field.id" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
||||||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate" [interfaceOptions]="{header: field.name}"
|
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate" [interfaceOptions]="{header: field.name}"
|
||||||
interface="alert">
|
interface="alert">
|
||||||
<ion-select-option value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
<ion-select-option *ngIf="!editMode || !field.required" value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
||||||
<ion-select-option *ngFor="let option of options" [value]="option">
|
<ion-select-option *ngFor="let option of options" [value]="option">
|
||||||
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||||
|
|
|
@ -35,6 +35,7 @@ export class AddonModDataFieldRadiobuttonComponent extends AddonModDataFieldPlug
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options = this.field.param1.split('\n');
|
this.options = this.field.param1.split('\n');
|
||||||
|
this.options = this.options.filter((option) => option !== '');
|
||||||
|
|
||||||
let val: string | undefined;
|
let val: string | undefined;
|
||||||
if (this.editMode && this.value) {
|
if (this.editMode && this.value) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
:host .addon-mod_forum-post {
|
:host .addon-mod_forum-post {
|
||||||
background-color: var(--ion-item-background);
|
background-color: var(--ion-item-background);
|
||||||
border-bottom: 1px solid var(--addon-forum-border-color);
|
border-bottom: var(--addon-forum-border-color, 1px) solid var(--addon-forum-border-color);
|
||||||
|
|
||||||
.addon-forum-star {
|
.addon-forum-star {
|
||||||
color: var(--core-star-color);
|
color: var(--core-star-color);
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
:host {
|
:host {
|
||||||
|
|
||||||
.addon-forum-reply-button .label {
|
.addon-forum-reply-button ion-label {
|
||||||
margin: 0;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-card addon-mod-forum-post {
|
||||||
|
--addon-forum-border-color: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,7 @@
|
||||||
<form *ngIf="showForm" #newDiscFormEl>
|
<form *ngIf="showForm" #newDiscFormEl>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-input labelPlacement="stacked" [(ngModel)]="newDiscussion.subject" type="text"
|
<ion-input labelPlacement="stacked" [(ngModel)]="newDiscussion.subject" type="text"
|
||||||
[placeholder]="'addon.mod_forum.subject' | translate" name="subject">
|
[placeholder]="'addon.mod_forum.subject' | translate" name="subject" [label]="'addon.mod_forum.subject' | translate" />
|
||||||
<p>{{ 'addon.mod_forum.subject' | translate }}</p>
|
|
||||||
</ion-input>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||||
|
@ -38,7 +36,7 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div *ngIf="advanced" id="addon-mod-forum-new-discussion-advanced">
|
<div *ngIf="advanced" id="addon-mod-forum-new-discussion-advanced">
|
||||||
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups" class="ion-no-validation">
|
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups" class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups">
|
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups">
|
||||||
{{ 'addon.mod_forum.posttomygroups' | translate }}
|
{{ 'addon.mod_forum.posttomygroups' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -54,12 +52,12 @@
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="newDiscussion.subscribe" name="subscribe">
|
<ion-toggle [(ngModel)]="newDiscussion.subscribe" name="subscribe">
|
||||||
{{ 'addon.mod_forum.discussionsubscription' | translate }}
|
{{ 'addon.mod_forum.discussionsubscription' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="canPin" class="ion-text-wrap ion-no-validation">
|
<ion-item *ngIf="canPin" class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="newDiscussion.pin" name="pin">
|
<ion-toggle [(ngModel)]="newDiscussion.pin" name="pin">
|
||||||
{{ 'addon.mod_forum.discussionpinned' | translate }}
|
{{ 'addon.mod_forum.discussionpinned' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -68,7 +66,8 @@
|
||||||
[maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid"
|
[maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid"
|
||||||
[allowOffline]="true" [courseId]="courseId" />
|
[allowOffline]="true" [courseId]="courseId" />
|
||||||
</div>
|
</div>
|
||||||
<ion-item *ngIf="showGroups && postInGroupMessage && !newDiscussion.postToAllGroups" class="addon-forum-group-info">
|
<ion-item *ngIf="showGroups && postInGroupMessage && !newDiscussion.postToAllGroups"
|
||||||
|
class="addon-forum-group-info ion-text-wrap">
|
||||||
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<core-format-text [text]="postInGroupMessage" contextLevel="course" [contextInstanceId]="courseId"
|
<core-format-text [text]="postInGroupMessage" contextLevel="course" [contextInstanceId]="courseId"
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
@ -17,6 +17,7 @@
|
||||||
<ion-input labelPlacement="stacked" type="text" [placeholder]="'addon.mod_glossary.concept' | translate"
|
<ion-input labelPlacement="stacked" type="text" [placeholder]="'addon.mod_glossary.concept' | translate"
|
||||||
[(ngModel)]="data.concept" name="concept" [label]="'addon.mod_glossary.concept' | translate" />
|
[(ngModel)]="data.concept" name="concept" [label]="'addon.mod_glossary.concept' | translate" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label position="stacked">{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
|
<ion-label position="stacked">{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
|
||||||
<core-rich-text-editor [control]="definitionControl" (contentChanged)="onDefinitionChange($event)"
|
<core-rich-text-editor [control]="definitionControl" (contentChanged)="onDefinitionChange($event)"
|
||||||
|
@ -24,8 +25,8 @@
|
||||||
[componentId]="cmId" [autoSave]="true" contextLevel="module" [contextInstanceId]="cmId" elementId="definition_editor"
|
[componentId]="cmId" [autoSave]="true" contextLevel="module" [contextInstanceId]="cmId" elementId="definition_editor"
|
||||||
[draftExtraParams]="editorExtraParams" />
|
[draftExtraParams]="editorExtraParams" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="categories.length > 0">
|
|
||||||
|
|
||||||
|
<ion-item *ngIf="categories.length > 0">
|
||||||
<ion-select labelPlacement="stacked" [(ngModel)]="data.categories" multiple="true" interface="action-sheet"
|
<ion-select labelPlacement="stacked" [(ngModel)]="data.categories" multiple="true" interface="action-sheet"
|
||||||
[placeholder]="'addon.mod_glossary.categories' | translate" name="categories" [cancelText]="'core.cancel' | translate"
|
[placeholder]="'addon.mod_glossary.categories' | translate" name="categories" [cancelText]="'core.cancel' | translate"
|
||||||
[interfaceOptions]="{header: 'addon.mod_glossary.categories' | translate}"
|
[interfaceOptions]="{header: 'addon.mod_glossary.categories' | translate}"
|
||||||
|
@ -35,40 +36,38 @@
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="showAliases">
|
|
||||||
|
|
||||||
|
<ion-item *ngIf="showAliases">
|
||||||
<ion-textarea labelPlacement="stacked" [(ngModel)]="data.aliases" rows="1" [core-auto-rows]="data.aliases" name="aliases"
|
<ion-textarea labelPlacement="stacked" [(ngModel)]="data.aliases" rows="1" [core-auto-rows]="data.aliases" name="aliases"
|
||||||
[label]="'addon.mod_glossary.aliases' | translate" />
|
[label]="'addon.mod_glossary.aliases' | translate" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item-divider>
|
|
||||||
<ion-label>
|
|
||||||
<h2>{{ 'addon.mod_glossary.attachment' | translate }}</h2>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item-divider>
|
|
||||||
<core-attachments [files]="data.attachments" [component]="component" [componentId]="glossary.coursemodule" [allowOffline]="true"
|
<core-attachments [files]="data.attachments" [component]="component" [componentId]="glossary.coursemodule" [allowOffline]="true"
|
||||||
[courseId]="courseId" />
|
[courseId]="courseId" [title]="'addon.mod_glossary.attachment' | translate" />
|
||||||
|
|
||||||
<ng-container *ngIf="glossary.usedynalink">
|
<ng-container *ngIf="glossary.usedynalink">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_glossary.linking' | translate }}</h2>
|
<h2>{{ 'addon.mod_glossary.linking' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="data.usedynalink" name="usedynalink">
|
<ion-toggle [(ngModel)]="data.usedynalink" name="usedynalink">
|
||||||
{{ 'addon.mod_glossary.entryusedynalink' | translate }}
|
{{ 'addon.mod_glossary.entryusedynalink' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.casesensitive" name="casesensitive">
|
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.casesensitive" name="casesensitive">
|
||||||
{{ 'addon.mod_glossary.casesensitive' | translate }}
|
{{ 'addon.mod_glossary.casesensitive' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.fullmatch" name="fullmatch">
|
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.fullmatch" name="fullmatch">
|
||||||
{{ 'addon.mod_glossary.fullmatch' | translate }}
|
{{ 'addon.mod_glossary.fullmatch' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ion-button class="ion-margin" expand="block" [disabled]="!data.concept || !data.definition" (click)="save()">
|
<ion-button class="ion-margin" expand="block" [disabled]="!data.concept || !data.definition" (click)="save()">
|
||||||
{{ 'core.save' | translate }}
|
{{ 'core.save' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
|
@ -62,21 +62,21 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<ion-list *ngIf="ownAssessment && !assessment">
|
<ion-list *ngIf="ownAssessment && !assessment">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_workshop.yourassessment' | translate }}</h2>
|
<h2>{{ 'addon.mod_workshop.yourassessment' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item-divider>
|
||||||
<addon-mod-workshop-assessment [submission]="submission" [assessment]="ownAssessment" [courseId]="courseId" [access]="access"
|
<addon-mod-workshop-assessment [submission]="submission" [assessment]="ownAssessment" [courseId]="courseId" [access]="access"
|
||||||
[module]="module" [workshop]="workshop" />
|
[module]="module" [workshop]="workshop" />
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<ion-list *ngIf="submissionInfo && submissionInfo.reviewedby && submissionInfo.reviewedby.length && !assessment">
|
<ion-list *ngIf="submissionInfo && submissionInfo.reviewedby && submissionInfo.reviewedby.length && !assessment">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h3 class="item-heading">{{ 'addon.mod_workshop.receivedgrades' | translate }}</h3>
|
<h2>{{ 'addon.mod_workshop.receivedgrades' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item-divider>
|
||||||
<ng-container *ngFor="let reviewer of submissionInfo.reviewedby">
|
<ng-container *ngFor="let reviewer of submissionInfo.reviewedby">
|
||||||
<addon-mod-workshop-assessment *ngIf="!reviewer.ownAssessment" [submission]="submission" [assessment]="reviewer"
|
<addon-mod-workshop-assessment *ngIf="!reviewer.ownAssessment" [submission]="submission" [assessment]="reviewer"
|
||||||
[courseId]="courseId" [access]="access" [module]="module" [workshop]="workshop" />
|
[courseId]="courseId" [access]="access" [module]="module" [workshop]="workshop" />
|
||||||
|
@ -84,34 +84,34 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<ion-list *ngIf="submissionInfo && submissionInfo.reviewerof && submissionInfo.reviewerof.length && !assessment">
|
<ion-list *ngIf="submissionInfo && submissionInfo.reviewerof && submissionInfo.reviewerof.length && !assessment">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h3 class="item-heading">{{ 'addon.mod_workshop.givengrades' | translate }}</h3>
|
<h2>{{ 'addon.mod_workshop.givengrades' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item-divider>
|
||||||
<addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId"
|
<addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId"
|
||||||
[module]="module" [workshop]="workshop" [access]="access" />
|
[module]="module" [workshop]="workshop" [access]="access" />
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<form [formGroup]="feedbackForm" *ngIf="canAddFeedback && submission" #feedbackFormEl>
|
<form [formGroup]="feedbackForm" *ngIf="canAddFeedback && submission" #feedbackFormEl>
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h3 class="item-heading">{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h3>
|
<h2>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngIf="access.canpublishsubmissions">
|
<ion-item class="ion-text-wrap" lines="none" *ngIf="access.canpublishsubmissions">
|
||||||
<ion-toggle formControlName="published">
|
<ion-toggle formControlName="published">
|
||||||
<p class="item-heading">{{ 'addon.mod_workshop.publishsubmission' | translate }}</p>
|
<p class="item-heading">{{ 'addon.mod_workshop.publishsubmission' | translate }}</p>
|
||||||
<p>{{ 'addon.mod_workshop.publishsubmission_help' | translate }}</p>
|
<p>{{ 'addon.mod_workshop.publishsubmission_help' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h3 class="item-heading">{{ 'addon.mod_workshop.gradecalculated' | translate }}</h3>
|
<h3>{{ 'addon.mod_workshop.gradecalculated' | translate }}</h3>
|
||||||
<p>{{ submission.grade }}</p>
|
<p>{{ submission.grade }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-select labelPlacement="stacked" formControlName="grade" interface="action-sheet"
|
<ion-select labelPlacement="stacked" formControlName="grade" interface="action-sheet"
|
||||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'addon.mod_workshop.gradeover' | translate}"
|
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'addon.mod_workshop.gradeover' | translate}"
|
||||||
|
|
|
@ -21,12 +21,12 @@
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="preferencesLoaded">
|
<core-loading [hideUntil]="preferencesLoaded">
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngIf="preferences">
|
<ion-item class="ion-text-wrap" *ngIf="preferences" lines="none">
|
||||||
<ion-toggle [(ngModel)]="preferences.enableall" (ngModelChange)="enableAll(preferences.enableall)">
|
<ion-toggle [(ngModel)]="preferences.enableall" (ngModelChange)="enableAll(preferences.enableall)">
|
||||||
<p class="item-heading">{{ 'addon.notifications.allownotifications' | translate }}</p>
|
<p class="item-heading">{{ 'addon.notifications.allownotifications' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngIf="canChangeSound">
|
<ion-item class="ion-text-wrap" *ngIf="canChangeSound" lines="none">
|
||||||
<ion-toggle [(ngModel)]="notificationSound" (ngModelChange)="changeNotificationSound(notificationSound)">
|
<ion-toggle [(ngModel)]="notificationSound" (ngModelChange)="changeNotificationSound(notificationSound)">
|
||||||
<p class="item-heading">{{ 'addon.notifications.playsound' | translate }}</p>
|
<p class="item-heading">{{ 'addon.notifications.playsound' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ng-container *ngFor="let notification of component.notifications">
|
<ng-container *ngFor="let notification of component.notifications">
|
||||||
<!-- Tablet view -->
|
<!-- Tablet view -->
|
||||||
<ion-item class="ion-text-wrap ion-hide-md-down addon-notifications-table-content only-links ion-no-validation">
|
<ion-item class="ion-text-wrap ion-hide-md-down addon-notifications-table-content only-links" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<ion-row class="ion-no-padding ion-align-items-center">
|
<ion-row class="ion-no-padding ion-align-items-center">
|
||||||
<ion-col class="ion-margin-horizontal ion-no-padding">
|
<ion-col class="ion-margin-horizontal ion-no-padding">
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</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">
|
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" class="ion-text-wrap ion-hide-md-up" lines="none">
|
||||||
<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>
|
||||||
|
@ -152,7 +152,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">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p>{{ notification.displayname }}</p>
|
<p>{{ notification.displayname }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
OnInit,
|
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
|
@ -28,7 +27,6 @@ import {
|
||||||
import { BackButtonEvent } from '@ionic/core';
|
import { BackButtonEvent } from '@ionic/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { Translate } from '@singletons';
|
|
||||||
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
||||||
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab';
|
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
|
@ -38,10 +36,9 @@ import { CoreError } from './errors/error';
|
||||||
import { CorePromisedValue } from './promised-value';
|
import { CorePromisedValue } from './promised-value';
|
||||||
import { AsyncDirective } from './async-directive';
|
import { AsyncDirective } from './async-directive';
|
||||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||||
import { CorePlatform } from '@services/platform';
|
|
||||||
import { Swiper } from 'swiper';
|
import { Swiper } from 'swiper';
|
||||||
import { SwiperOptions } from 'swiper/types';
|
import { SwiperOptions } from 'swiper/types';
|
||||||
import { IonicSlides } from '@ionic/angular';
|
import { CoreSwiper } from '@singletons/swiper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to abstract some common code for tabs.
|
* Class to abstract some common code for tabs.
|
||||||
|
@ -49,7 +46,7 @@ import { IonicSlides } from '@ionic/angular';
|
||||||
@Component({
|
@Component({
|
||||||
template: '',
|
template: '',
|
||||||
})
|
})
|
||||||
export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, AfterViewInit, OnChanges, OnDestroy, AsyncDirective {
|
export class CoreTabsBaseComponent<T extends CoreTabBase> implements AfterViewInit, OnChanges, OnDestroy, AsyncDirective {
|
||||||
|
|
||||||
// Minimum tab's width.
|
// Minimum tab's width.
|
||||||
protected static readonly MIN_TAB_WIDTH = 107;
|
protected static readonly MIN_TAB_WIDTH = 107;
|
||||||
|
@ -59,32 +56,26 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
@Output() protected ionChange = new EventEmitter<T>(); // Emitted when the tab changes.
|
@Output() protected ionChange = new EventEmitter<T>(); // Emitted when the tab changes.
|
||||||
|
|
||||||
protected swiper?: Swiper;
|
protected swiper?: Swiper;
|
||||||
@ViewChild('swiperRef')
|
@ViewChild('swiperRef') set swiperRef(swiperRef: ElementRef) {
|
||||||
set swiperRef(swiperRef: ElementRef) {
|
|
||||||
/**
|
/**
|
||||||
* This setTimeout waits for Ionic's async initialization to complete.
|
* This setTimeout waits for Ionic's async initialization to complete.
|
||||||
* Otherwise, an outdated swiper reference will be used.
|
* Otherwise, an outdated swiper reference will be used.
|
||||||
*/
|
*/
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (swiperRef?.nativeElement?.swiper && !this.swiper) {
|
const swiper = CoreSwiper.initSwiperIfAvailable(this.swiper, swiperRef, this.swiperOpts);
|
||||||
this.swiper = swiperRef.nativeElement.swiper as Swiper;
|
if (!swiper) {
|
||||||
|
return;
|
||||||
this.swiper.changeLanguageDirection(CorePlatform.isRTL ? 'rtl' : 'ltr');
|
|
||||||
|
|
||||||
Object.keys(this.swiperOpts).forEach((key) => {
|
|
||||||
if (this.swiper) {
|
|
||||||
this.swiper.params[key] = this.swiperOpts[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to changes.
|
|
||||||
this.swiper.on('slideChangeTransitionEnd', () => {
|
|
||||||
this.slideChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
}
|
||||||
}, 0);
|
|
||||||
|
this.swiper = swiper;
|
||||||
|
|
||||||
|
// Subscribe to changes.
|
||||||
|
this.swiper.on('slideChangeTransitionEnd', () => {
|
||||||
|
this.slideChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs: T[] = []; // List of tabs.
|
tabs: T[] = []; // List of tabs.
|
||||||
|
@ -97,7 +88,6 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
numTabsShown = 0;
|
numTabsShown = 0;
|
||||||
description = '';
|
description = '';
|
||||||
swiperOpts: SwiperOptions = {
|
swiperOpts: SwiperOptions = {
|
||||||
modules: [IonicSlides],
|
|
||||||
slidesPerView: 3,
|
slidesPerView: 3,
|
||||||
centerInsufficientSlides: true,
|
centerInsufficientSlides: true,
|
||||||
threshold: 10,
|
threshold: 10,
|
||||||
|
@ -127,18 +117,6 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
CoreDirectivesRegistry.register(element.nativeElement, this);
|
CoreDirectivesRegistry.register(element.nativeElement, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async ngOnInit(): Promise<void> {
|
|
||||||
// Change the side when the language changes.
|
|
||||||
this.subscriptions.push(Translate.onLangChange.subscribe(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.swiper?.changeLanguageDirection(CorePlatform.isRTL ? 'rtl' : 'ltr');
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -55,6 +55,7 @@ export class CoreAttachmentsComponent implements OnInit {
|
||||||
@Input() acceptedTypes?: string; // List of supported filetypes. If undefined, all types supported.
|
@Input() acceptedTypes?: string; // List of supported filetypes. If undefined, all types supported.
|
||||||
@Input() required?: boolean; // Whether to display the required mark.
|
@Input() required?: boolean; // Whether to display the required mark.
|
||||||
@Input() courseId?: number; // Course ID.
|
@Input() courseId?: number; // Course ID.
|
||||||
|
@Input() title = Translate.instant('core.fileuploader.attachedfiles'); // Title to display.
|
||||||
|
|
||||||
maxSizeReadable?: string;
|
maxSizeReadable?: string;
|
||||||
maxSubmissionsReadable?: string;
|
maxSubmissionsReadable?: string;
|
||||||
|
|
|
@ -1,38 +1,42 @@
|
||||||
<core-loading [hideUntil]="loaded" [fullscreen]="false">
|
<core-loading [hideUntil]="loaded" [fullscreen]="false">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-card>
|
||||||
<ion-label>
|
<ion-item class="ion-text-wrap">
|
||||||
<span *ngIf="maxSubmissionsReadable">
|
<ion-label>
|
||||||
{{ 'core.maxsizeandattachments' | translate:{$a: {size: maxSizeReadable, attachments: maxSubmissionsReadable} } }}
|
<p class="item-heading">{{ title }} <span [core-mark-required]="required" class="core-mark-required"></span></p>
|
||||||
</span>
|
<span *ngIf="maxSubmissionsReadable">
|
||||||
<span *ngIf="!maxSubmissionsReadable">{{ 'core.maxfilesize' | translate:{$a: maxSizeReadable} }}</span>
|
{{ 'core.maxsizeandattachments' | translate:{$a: {size: maxSizeReadable, attachments: maxSubmissionsReadable} } }}
|
||||||
<span [core-mark-required]="required" class="core-mark-required"></span>
|
</span>
|
||||||
</ion-label>
|
<span *ngIf="!maxSubmissionsReadable">{{ 'core.maxfilesize' | translate:{$a: maxSizeReadable} }}</span>
|
||||||
</ion-item>
|
</ion-label>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="fileTypes && fileTypes.mimetypes && fileTypes.mimetypes.length">
|
<ion-button slot="end" (click)="add()" [attr.aria-label]="'core.fileuploader.addfiletext' | translate"
|
||||||
<ion-label>
|
*ngIf="unlimitedFiles || (maxSubmissions !== undefined && maxSubmissions >= 0 && files && files.length < maxSubmissions)">
|
||||||
<p>{{ 'core.fileuploader.filesofthesetypes' | translate }}</p>
|
<ion-icon name="fas-plus" slot="icon-only" aria-hidden="true" />
|
||||||
<ul class="list-with-style">
|
</ion-button>
|
||||||
<li *ngFor="let typeInfo of fileTypes.info">
|
</ion-item>
|
||||||
<strong *ngIf="typeInfo.name">{{typeInfo.name}} </strong>{{typeInfo.extlist}}
|
<ion-item class="ion-text-wrap" *ngIf="fileTypes && fileTypes.mimetypes && fileTypes.mimetypes.length">
|
||||||
</li>
|
<ion-label>
|
||||||
</ul>
|
<p>{{ 'core.fileuploader.filesofthesetypes' | translate }}</p>
|
||||||
</ion-label>
|
<ul class="list-with-style">
|
||||||
</ion-item>
|
<li *ngFor="let typeInfo of fileTypes.info">
|
||||||
<div *ngFor="let file of files; let index=index">
|
<strong *ngIf="typeInfo.name">{{typeInfo.name}} </strong>{{typeInfo.extlist}}
|
||||||
<!-- Files already attached to the submission, either in online or in offline. -->
|
</li>
|
||||||
<core-file *ngIf="!file.name" [file]="file" [component]="component" [componentId]="componentId" [canDelete]="true"
|
</ul>
|
||||||
(onDelete)="delete(index, true)" [canDownload]="!file.offline" />
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ng-container *ngFor="let file of files; let index=index">
|
||||||
|
<!-- Files already attached to the submission, either in online or in offline. -->
|
||||||
|
<core-file *ngIf="!file.name" [file]="file" [component]="component" [componentId]="componentId" [canDelete]="true"
|
||||||
|
(onDelete)="delete(index, true)" [canDownload]="!file.offline" />
|
||||||
|
|
||||||
<!-- Files added to draft but not attached to submission yet. -->
|
<!-- Files added to draft but not attached to submission yet. -->
|
||||||
<core-local-file *ngIf="file.name" [file]="file" [manage]="true" (onDelete)="delete(index, false)"
|
<core-local-file *ngIf="file.name" [file]="file" [manage]="true" (onDelete)="delete(index, false)"
|
||||||
(onRename)="renamed(index, $event)" />
|
(onRename)="renamed(index, $event)" />
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Button to add more files. -->
|
<ion-item class="ion-text-wrap" *ngIf="!files || !files.length">
|
||||||
<ion-button expand="block"
|
<ion-label>
|
||||||
*ngIf="unlimitedFiles || (maxSubmissions !== undefined && maxSubmissions >= 0 && files && files.length < maxSubmissions)"
|
<p>{{ 'core.fileuploader.nofilesattached' | translate }}</p>
|
||||||
class="ion-text-wrap ion-margin" (click)="add()">
|
</ion-label>
|
||||||
<ion-icon name="fas-plus" slot="start" aria-hidden="true" />
|
</ion-item>
|
||||||
{{ 'core.fileuploader.addfiletext' | translate }}
|
</ion-card>
|
||||||
</ion-button>
|
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<ion-list-header *ngIf="title">
|
<ion-list-header *ngIf="title">
|
||||||
<ion-label>{{title}}</ion-label>
|
<ion-label>{{title}}</ion-label>
|
||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" *ngFor="let item of items" core-link [capture]="item.captureLink"
|
<ion-item class="ion-text-wrap" lines="none" *ngFor="let item of items" core-link [capture]="item.captureLink"
|
||||||
[autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction"
|
[autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction"
|
||||||
[hidden]="item.hidden" [detail]="!!(item.href && !item.iconAction)" role="menuitem" [button]="!!(item.href && !item.iconAction)"
|
[hidden]="item.hidden" [detail]="!!(item.href && !item.iconAction)" role="menuitem" [button]="!!(item.href && !item.iconAction)"
|
||||||
[showBrowserWarning]="item.showBrowserWarning">
|
[showBrowserWarning]="item.showBrowserWarning">
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
<ng-container *ngIf="groupInfo && groupInfo.groups.length > 0 && (groupInfo.separateGroups || groupInfo.visibleGroups)">
|
<ng-container *ngIf="groupInfo && groupInfo.groups.length > 0 && (groupInfo.separateGroups || groupInfo.visibleGroups)">
|
||||||
<ion-card class="core-info-card" *ngIf="multipleGroupsMessage && groupInfo.groups && groupInfo.groups.length > 1">
|
<ion-card class="core-info-card" *ngIf="multipleGroupsMessage && groupInfo.groups && groupInfo.groups.length > 1">
|
||||||
<ion-item>
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-icon name="fas-circle-question" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-circle-question" slot="start" aria-hidden="true" />
|
||||||
<ion-label>{{ multipleGroupsMessage }}</ion-label>
|
<ion-label>
|
||||||
|
<p class="item-label">{{ multipleGroupsMessage }}</p>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap core-group-selector">
|
<ion-item class="ion-text-wrap core-group-selector" lines="full">
|
||||||
<ion-select [label]="(groupInfo.separateGroups ? 'core.groupsseparate': 'core.groupsvisible') | translate" [(ngModel)]=" selected"
|
<ion-icon name="fas-user-group" slot="start" aria-hidden="true" />
|
||||||
(ionChange)="selectedChange.emit(selected)" interface="action-sheet" [cancelText]="'core.cancel' | translate"
|
<ion-select [(ngModel)]=" selected" (ionChange)="selectedChange.emit(selected)" interface="action-sheet"
|
||||||
[interfaceOptions]="{header: 'core.group' | translate}">
|
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||||
|
<p class="item-heading" slot="label">{{ (groupInfo.separateGroups ? 'core.groupsseparate': 'core.groupsvisible') | translate }}
|
||||||
|
</p>
|
||||||
<ion-select-option *ngFor="let group of groupInfo.groups" [value]=" group.id">
|
<ion-select-option *ngFor="let group of groupInfo.groups" [value]=" group.id">
|
||||||
<core-format-text [text]="group.name" contextLevel="course" [contextInstanceId]="courseId" [wsNotFiltered]="true" />
|
<core-format-text [text]="group.name" contextLevel="course" [contextInstanceId]="courseId" [wsNotFiltered]="true" />
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.core-group-selector ion-icon {
|
||||||
|
--webkit-margin-end: 16px;
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import { CoreGroupInfo } from '@services/groups';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-group-selector',
|
selector: 'core-group-selector',
|
||||||
templateUrl: 'group-selector.html',
|
templateUrl: 'group-selector.html',
|
||||||
|
styleUrl: 'group-selector.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CoreGroupSelectorComponent {
|
export class CoreGroupSelectorComponent {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<ng-container *ngIf="loaded">
|
<ng-container *ngIf="loaded && !svgLoaded">
|
||||||
<img *ngIf="!isLocalUrl" [src]="iconUrl" core-external-content alt="" [component]="linkIconWithComponent ? modname : null"
|
<img *ngIf="!isLocalUrl" [src]="iconUrl" core-external-content alt="" [component]="linkIconWithComponent ? modname : null"
|
||||||
[componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()">
|
[componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()">
|
||||||
<img *ngIf="isLocalUrl" [src]="iconUrl" (error)="loadFallbackIcon()" alt="">
|
<img *ngIf="isLocalUrl" [src]="iconUrl" (error)="loadFallbackIcon()" alt="">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<div [hidden]="!svgLoaded" #svg></div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants, ModPurpose } from '@/core/constants';
|
import { CoreConstants, ModPurpose } from '@/core/constants';
|
||||||
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
|
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange, ViewChild } from '@angular/core';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
import { CoreFile } from '@services/file';
|
import { CoreFile } from '@services/file';
|
||||||
|
@ -63,10 +63,13 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
return this.showAlt ? this.modNameTranslated : '';
|
return this.showAlt ? this.modNameTranslated : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewChild('svg') svgElement!: ElementRef<HTMLElement>;
|
||||||
|
|
||||||
iconUrl = '';
|
iconUrl = '';
|
||||||
|
|
||||||
modNameTranslated = '';
|
modNameTranslated = '';
|
||||||
isLocalUrl = false;
|
isLocalUrl = false;
|
||||||
|
svgLoaded = false;
|
||||||
linkIconWithComponent = false;
|
linkIconWithComponent = false;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
|
@ -141,6 +144,10 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl);
|
this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl);
|
||||||
|
|
||||||
|
if (this.isBranded !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If it's an Moodle Theme icon, check if filtericon is set and use it.
|
// If it's an Moodle Theme icon, check if filtericon is set and use it.
|
||||||
if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
|
if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
|
||||||
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl, 'filtericon');
|
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl, 'filtericon');
|
||||||
|
@ -296,6 +303,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
protected async setSVGIcon(): Promise<void> {
|
protected async setSVGIcon(): Promise<void> {
|
||||||
if (this.iconVersion === IconVersion.LEGACY_VERSION) {
|
if (this.iconVersion === IconVersion.LEGACY_VERSION) {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
this.svgLoaded = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -338,6 +346,8 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mimetype !== 'image/svg+xml' || !fileContents) {
|
if (mimetype !== 'image/svg+xml' || !fileContents) {
|
||||||
|
this.svgLoaded = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,6 +357,8 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
// Safety check.
|
// Safety check.
|
||||||
if (doc.documentElement.nodeName !== 'svg') {
|
if (doc.documentElement.nodeName !== 'svg') {
|
||||||
|
this.svgLoaded = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,9 +396,10 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.element.replaceChildren(doc.documentElement);
|
this.svgElement.nativeElement.replaceChildren(doc.documentElement);
|
||||||
|
this.svgLoaded = true;
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors.
|
this.svgLoaded = false;
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreMath } from '@singletons/math';
|
import { CoreMath } from '@singletons/math';
|
||||||
|
import { CoreSwiper } from '@singletons/swiper';
|
||||||
import { Swiper } from 'swiper';
|
import { Swiper } from 'swiper';
|
||||||
import { SwiperOptions } from 'swiper/types';
|
import { SwiperOptions } from 'swiper/types';
|
||||||
/**
|
/**
|
||||||
|
@ -42,29 +43,28 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
||||||
@Output() onDidChange = new EventEmitter<CoreSwipeCurrentItemData<Item>>();
|
@Output() onDidChange = new EventEmitter<CoreSwipeCurrentItemData<Item>>();
|
||||||
|
|
||||||
protected swiper?: Swiper;
|
protected swiper?: Swiper;
|
||||||
@ViewChild('swiperRef')
|
@ViewChild('swiperRef') set swiperRef(swiperRef: ElementRef) {
|
||||||
set swiperRef(swiperRef: ElementRef) {
|
|
||||||
/**
|
/**
|
||||||
* This setTimeout waits for Ionic's async initialization to complete.
|
* This setTimeout waits for Ionic's async initialization to complete.
|
||||||
* Otherwise, an outdated swiper reference will be used.
|
* Otherwise, an outdated swiper reference will be used.
|
||||||
*/
|
*/
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (swiperRef?.nativeElement?.swiper) {
|
const swiper = CoreSwiper.initSwiperIfAvailable(this.swiper, swiperRef, this.options);
|
||||||
this.swiper = swiperRef.nativeElement.swiper as Swiper;
|
if (!swiper) {
|
||||||
|
return;
|
||||||
await this.initialize();
|
|
||||||
|
|
||||||
if (this.options.initialSlide) {
|
|
||||||
this.swiper.slideTo(this.options.initialSlide, 0, this.options.runCallbacksOnInit);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateOptions();
|
|
||||||
|
|
||||||
this.swiper.on('slideChangeTransitionStart', () => this.slideWillChange());
|
|
||||||
this.swiper.on('slideChangeTransitionEnd', () => this.slideDidChange());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}, 0);
|
|
||||||
|
this.swiper = swiper;
|
||||||
|
|
||||||
|
await this.initialize();
|
||||||
|
|
||||||
|
if (this.options.initialSlide) {
|
||||||
|
this.swiper.slideTo(this.options.initialSlide, 0, this.options.runCallbacksOnInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.swiper.on('slideChangeTransitionStart', () => this.slideWillChange());
|
||||||
|
this.swiper.on('slideChangeTransitionEnd', () => this.slideDidChange());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContentChild(TemplateRef) template?: TemplateRef<{item: Item; active: boolean}>; // Template defined by the content.
|
@ContentChild(TemplateRef) template?: TemplateRef<{item: Item; active: boolean}>; // Template defined by the content.
|
||||||
|
@ -173,17 +173,21 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
||||||
// If slides are being updated, wait for the update to finish.
|
// If slides are being updated, wait for the update to finish.
|
||||||
await this.ready();
|
await this.ready();
|
||||||
|
|
||||||
|
if (!this.swiper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the number of slides matches the number of items.
|
// Verify that the number of slides matches the number of items.
|
||||||
const slidesLength = this.swiper?.slides?.length || 0;
|
const slidesLength = this.swiper.slides?.length || 0;
|
||||||
if (slidesLength !== this.items.length) {
|
if (slidesLength !== this.items.length) {
|
||||||
// Number doesn't match, do a new update to try to match them.
|
// Number doesn't match, do a new update to try to match them.
|
||||||
await this.updateSlidesComponent();
|
await this.updateSlidesComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.swiper?.slides) {
|
if (!this.swiper.slides) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.swiper?.slideTo(index, speed, runCallbacks);
|
this.swiper.slideTo(index, speed, runCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,7 +199,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
||||||
*/
|
*/
|
||||||
async slideToItem(item: Item, speed?: number, runCallbacks?: boolean): Promise<void> {
|
async slideToItem(item: Item, speed?: number, runCallbacks?: boolean): Promise<void> {
|
||||||
const index = this.manager?.getSource().getItemIndex(item) ?? -1;
|
const index = this.manager?.getSource().getItemIndex(item) ?? -1;
|
||||||
if (index != -1) {
|
if (index !== -1) {
|
||||||
await this.slideToIndex(index, speed, runCallbacks);
|
await this.slideToIndex(index, speed, runCallbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,15 +252,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.swiper.params === undefined) {
|
CoreSwiper.updateOptions(this.swiper, this.options);
|
||||||
this.swiper.params = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(this.options).forEach((key) => {
|
|
||||||
if (this.swiper) {
|
|
||||||
this.swiper.params[key] = this.options[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
|
@ -52,7 +51,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||||
styleUrls: ['../tabs/tabs.scss'],
|
styleUrls: ['../tabs/tabs.scss'],
|
||||||
})
|
})
|
||||||
export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutletTab>
|
export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutletTab>
|
||||||
implements OnInit, AfterViewInit, OnChanges, OnDestroy {
|
implements AfterViewInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine tabs layout.
|
* Determine tabs layout.
|
||||||
|
|
|
@ -98,16 +98,18 @@
|
||||||
.core-textarea {
|
.core-textarea {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
--highlight-color: transparent !important;
|
||||||
|
|
||||||
::ng-deep textarea {
|
::ng-deep textarea {
|
||||||
margin: 0 !important;
|
margin: 0px !important;
|
||||||
padding: 0;
|
padding: 0px;
|
||||||
resize: none;
|
resize: none;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: auto;
|
height: auto;
|
||||||
top: 0;
|
top: 0px;
|
||||||
bottom: 0;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +182,10 @@
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.item-label-stacked) {
|
:host-context(.item) {
|
||||||
margin-top: 10px;
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.item-label-stacked) {
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import { IonTextarea, IonContent, IonicSlides } from '@ionic/angular';
|
import { IonTextarea, IonContent } from '@ionic/angular';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
@ -33,7 +33,6 @@ import { CoreFilepool } from '@services/filepool';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUrlUtils } from '@services/utils/url';
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
|
||||||
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreEditorOffline } from '../../services/editor-offline';
|
import { CoreEditorOffline } from '../../services/editor-offline';
|
||||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||||
|
@ -45,6 +44,7 @@ import { CorePlatform } from '@services/platform';
|
||||||
import { Swiper } from 'swiper';
|
import { Swiper } from 'swiper';
|
||||||
import { SwiperOptions } from 'swiper/types';
|
import { SwiperOptions } from 'swiper/types';
|
||||||
import { ContextLevel } from '@/core/constants';
|
import { ContextLevel } from '@/core/constants';
|
||||||
|
import { CoreSwiper } from '@singletons/swiper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a rich text editor if enabled.
|
* Component to display a rich text editor if enabled.
|
||||||
|
@ -81,26 +81,21 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
@ViewChild('editor') editor?: ElementRef; // WYSIWYG editor.
|
@ViewChild('editor') editor?: ElementRef; // WYSIWYG editor.
|
||||||
@ViewChild('textarea') textarea?: IonTextarea; // Textarea editor.
|
@ViewChild('textarea') textarea?: IonTextarea; // Textarea editor.
|
||||||
@ViewChild('toolbar') toolbar?: ElementRef;
|
@ViewChild('toolbar') toolbar?: ElementRef;
|
||||||
|
|
||||||
protected toolbarSlides?: Swiper;
|
protected toolbarSlides?: Swiper;
|
||||||
@ViewChild('swiperRef')
|
@ViewChild('swiperRef') set swiperRef(swiperRef: ElementRef) {
|
||||||
set swiperRef(swiperRef: ElementRef) {
|
|
||||||
/**
|
/**
|
||||||
* This setTimeout waits for Ionic's async initialization to complete.
|
* This setTimeout waits for Ionic's async initialization to complete.
|
||||||
* Otherwise, an outdated swiper reference will be used.
|
* Otherwise, an outdated swiper reference will be used.
|
||||||
*/
|
*/
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (swiperRef.nativeElement?.swiper) {
|
const swiper = CoreSwiper.initSwiperIfAvailable(this.toolbarSlides, swiperRef, this.swiperOpts);
|
||||||
this.toolbarSlides = swiperRef.nativeElement.swiper as Swiper;
|
if (!swiper) {
|
||||||
|
return;
|
||||||
this.toolbarSlides.changeLanguageDirection(CorePlatform.isRTL ? 'rtl' : 'ltr');
|
|
||||||
|
|
||||||
Object.keys(this.swiperOpts).forEach((key) => {
|
|
||||||
if (this.toolbarSlides) {
|
|
||||||
this.toolbarSlides.params[key] = this.swiperOpts[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, 0);
|
|
||||||
|
this.toolbarSlides = swiper;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly DRAFT_AUTOSAVE_FREQUENCY = 30000;
|
protected readonly DRAFT_AUTOSAVE_FREQUENCY = 30000;
|
||||||
|
@ -128,7 +123,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
protected originalContent?: string;
|
protected originalContent?: string;
|
||||||
protected resizeFunction?: () => Promise<number>;
|
protected resizeFunction?: () => Promise<number>;
|
||||||
protected selectionChangeFunction = (): void => this.updateToolbarStyles();
|
protected selectionChangeFunction = (): void => this.updateToolbarStyles();
|
||||||
protected languageChangedSubscription?: Subscription;
|
|
||||||
protected resizeListener?: CoreEventObserver;
|
protected resizeListener?: CoreEventObserver;
|
||||||
protected domPromise?: CoreCancellablePromise<void>;
|
protected domPromise?: CoreCancellablePromise<void>;
|
||||||
protected buttonsDomPromise?: CoreCancellablePromise<void>;
|
protected buttonsDomPromise?: CoreCancellablePromise<void>;
|
||||||
|
@ -159,7 +153,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
isEmpty = true;
|
isEmpty = true;
|
||||||
|
|
||||||
swiperOpts: SwiperOptions = {
|
swiperOpts: SwiperOptions = {
|
||||||
modules: [IonicSlides],
|
|
||||||
slidesPerView: 6,
|
slidesPerView: 6,
|
||||||
centerInsufficientSlides: true,
|
centerInsufficientSlides: true,
|
||||||
watchSlidesProgress: true,
|
watchSlidesProgress: true,
|
||||||
|
@ -301,13 +294,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
// Check the height again, now the window height should have been updated.
|
// Check the height again, now the window height should have been updated.
|
||||||
this.maximizeEditorSize();
|
this.maximizeEditorSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change the side when the language changes.
|
|
||||||
this.languageChangedSubscription = Translate.onLangChange.subscribe(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.toolbarSlides?.changeLanguageDirection(CorePlatform.isRTL ? 'rtl' : 'ltr');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1120,7 +1106,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.valueChangeSubscription?.unsubscribe();
|
this.valueChangeSubscription?.unsubscribe();
|
||||||
this.languageChangedSubscription?.unsubscribe();
|
|
||||||
|
|
||||||
document.removeEventListener('selectionchange', this.selectionChangeFunction);
|
document.removeEventListener('selectionchange', this.selectionChangeFunction);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"addfiletext": "Add file",
|
"addfiletext": "Add file",
|
||||||
|
"attachedfiles": "Attached files",
|
||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"audiotitle": "Record audio",
|
"audiotitle": "Record audio",
|
||||||
"camera": "Camera",
|
"camera": "Camera",
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
"microphonepermissiondenied": "Permission to access the microphone has been denied.",
|
"microphonepermissiondenied": "Permission to access the microphone has been denied.",
|
||||||
"microphonepermissionrestricted": "Microphone access is restricted.",
|
"microphonepermissionrestricted": "Microphone access is restricted.",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
|
"nofilesattached": "No files attached",
|
||||||
"pauserecording": "Pause recording",
|
"pauserecording": "Pause recording",
|
||||||
"photoalbums": "Photo albums",
|
"photoalbums": "Photo albums",
|
||||||
"readingfile": "Reading file",
|
"readingfile": "Reading file",
|
||||||
|
|
|
@ -204,7 +204,7 @@ export class CorePushNotificationsProvider {
|
||||||
protected async initializeDatabase(): Promise<void> {
|
protected async initializeDatabase(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await CoreApp.createTablesFromSchema(APP_SCHEMA);
|
await CoreApp.createTablesFromSchema(APP_SCHEMA);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<ion-item class="ion-text-wrap" *ngIf="item && (item!.canrate || item!.rating !== null) && !disabled">
|
<ion-item class="ion-text-wrap" *ngIf="item && (item!.canrate || item!.rating !== null) && !disabled" lines="none">
|
||||||
<ion-select class="ion-text-start" [(ngModel)]="rating" (ngModelChange)="userRatingChanged()" interface="action-sheet"
|
<ion-select class="ion-text-start" [(ngModel)]="rating" (ngModelChange)="userRatingChanged()" interface="action-sheet"
|
||||||
[cancelText]="'core.cancel' | translate" [disabled]="!item!.canrate" [interfaceOptions]="{header: 'core.rating.rating' | translate}"
|
[cancelText]="'core.cancel' | translate" [disabled]="!item!.canrate" [interfaceOptions]="{header: 'core.rating.rating' | translate}"
|
||||||
[label]="'core.rating.rating' | translate">
|
[label]="'core.rating.rating' | translate">
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list class="list-item-limited-width">
|
<ion-list class="list-item-limited-width">
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="rtl" (ionChange)="RTLChanged()">
|
<ion-toggle [(ngModel)]="rtl" (ionChange)="RTLChanged()">
|
||||||
<p class="item-heading">Change text direction</p>
|
<p class="item-heading">Change text direction</p>
|
||||||
<p>{{ direction }}</p>
|
<p>{{ direction }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="forceSafeAreaMargins" (ionChange)="safeAreaChanged()">
|
<ion-toggle [(ngModel)]="forceSafeAreaMargins" (ionChange)="safeAreaChanged()">
|
||||||
<p class="item-heading">Force safe area margins</p>
|
<p class="item-heading">Force safe area margins</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
@ -34,13 +34,13 @@
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-container *ngIf="siteId">
|
<ng-container *ngIf="siteId">
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="remoteStyles" (ionChange)="remoteStylesChanged()">
|
<ion-toggle [(ngModel)]="remoteStyles" (ionChange)="remoteStylesChanged()">
|
||||||
<p class="item-heading">Enable remote styles <ion-badge>{{remoteStylesCount}}</ion-badge>
|
<p class="item-heading">Enable remote styles <ion-badge>{{remoteStylesCount}}</ion-badge>
|
||||||
</p>
|
</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="pluginStyles" (ionChange)="pluginStylesChanged()">
|
<ion-toggle [(ngModel)]="pluginStyles" (ionChange)="pluginStylesChanged()">
|
||||||
<p class="item-heading">Enable site plugin styles <ion-badge>{{pluginStylesCount}}</ion-badge>
|
<p class="item-heading">Enable site plugin styles <ion-badge>{{pluginStylesCount}}</ion-badge>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -11,14 +11,14 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list class="list-item-limited-width">
|
<ion-list class="list-item-limited-width">
|
||||||
<ion-item class="ion-text-wrap ion-no-validation" lines="none">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-select [(ngModel)]="selectedLanguage" (ionChange)="languageChanged($event)" interface="action-sheet"
|
<ion-select [(ngModel)]="selectedLanguage" (ionChange)="languageChanged($event)" interface="action-sheet"
|
||||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.settings.language' | translate}">
|
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.settings.language' | translate}">
|
||||||
<div slot="label" class="item-heading ion-text-wrap">{{ 'core.settings.language' | translate }}</div>
|
<div slot="label" class="item-heading ion-text-wrap">{{ 'core.settings.language' | translate }}</div>
|
||||||
<ion-select-option *ngFor="let entry of languages" [value]="entry.code">{{ entry.name }}</ion-select-option>
|
<ion-select-option *ngFor="let entry of languages" [value]="entry.code">{{ entry.name }}</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap core-settings-general-font-size item-interactive ion-no-validation" lines="none">
|
<ion-item class="ion-text-wrap core-settings-general-font-size item-interactive" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ 'core.settings.fontsize' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'core.settings.fontsize' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</ion-segment-button>
|
</ion-segment-button>
|
||||||
</ion-segment>
|
</ion-segment>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap core-settings-general-color-scheme ion-no-validation" *ngIf="colorSchemes.length > 0" lines="none">
|
<ion-item class="ion-text-wrap core-settings-general-color-scheme" *ngIf="colorSchemes.length > 0" lines="none">
|
||||||
<ion-select [(ngModel)]="selectedScheme" (ionChange)="colorSchemeChanged($event)" interface="action-sheet"
|
<ion-select [(ngModel)]="selectedScheme" (ionChange)="colorSchemeChanged($event)" interface="action-sheet"
|
||||||
[cancelText]="'core.cancel' | translate" [disabled]="colorSchemeDisabled"
|
[cancelText]="'core.cancel' | translate" [disabled]="colorSchemeDisabled"
|
||||||
[interfaceOptions]="{header: 'core.settings.colorscheme' | translate}">
|
[interfaceOptions]="{header: 'core.settings.colorscheme' | translate}">
|
||||||
|
@ -45,18 +45,18 @@
|
||||||
{{ 'core.settings.colorscheme-' + scheme | translate }}</ion-select-option>
|
{{ 'core.settings.colorscheme-' + scheme | translate }}</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="colorSchemes.length> 0 && selectedScheme==='system' && isAndroid" lines="none">
|
<ion-item *ngIf="colorSchemes.length > 0 && selectedScheme === 'system' && isAndroid" lines="none">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p class="ion-text-wrap">{{ 'core.settings.colorscheme-system-notice' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'core.settings.colorscheme-system-notice' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item lines="none">
|
||||||
<ion-toggle [(ngModel)]="richTextEditor" (ionChange)="richTextEditorChanged($event)">
|
<ion-toggle [(ngModel)]="richTextEditor" (ionChange)="richTextEditorChanged($event)">
|
||||||
<p class="item-heading ion-text-wrap">{{ 'core.settings.enablerichtexteditor' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'core.settings.enablerichtexteditor' | translate }}</p>
|
||||||
<p class="ion-text-wrap">{{ 'core.settings.enablerichtexteditordescription' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'core.settings.enablerichtexteditordescription' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="displayIframeHelp" class="ion-no-validation">
|
<ion-item *ngIf="displayIframeHelp" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ 'core.settings.ioscookies' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'core.settings.ioscookies' | translate }}</p>
|
||||||
<p class="ion-text-wrap">{{ 'core.settings.ioscookiesdescription' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'core.settings.ioscookiesdescription' | translate }}</p>
|
||||||
|
@ -65,13 +65,13 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-no-validation">
|
<ion-item lines="none">
|
||||||
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)">
|
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)">
|
||||||
<p class="item-heading ion-text-wrap">{{ 'core.settings.debugdisplay' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'core.settings.debugdisplay' | translate }}</p>
|
||||||
<p class="ion-text-wrap">{{ 'core.settings.debugdisplaydescription' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'core.settings.debugdisplaydescription' | translate }}</p>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="analyticsAvailable" class="ion-no-validation">
|
<ion-item *ngIf="analyticsAvailable" lines="none">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ 'core.settings.enableanalytics' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'core.settings.enableanalytics' | translate }}</p>
|
||||||
<p class="ion-text-wrap">{{ 'core.settings.enableanalyticsdescription' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'core.settings.enableanalyticsdescription' | translate }}</p>
|
||||||
|
|
|
@ -168,6 +168,9 @@ export class CoreSettingsGeneralPage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply language changes and restart the app.
|
* Apply language changes and restart the app.
|
||||||
|
*
|
||||||
|
* IMPORTANT NOTE: If for any reason we decide to remove this method,
|
||||||
|
* we'll need to listen to lang change on Slides to change direction.
|
||||||
*/
|
*/
|
||||||
protected async applyLanguageAndRestart(): Promise<void> {
|
protected async applyLanguageAndRestart(): Promise<void> {
|
||||||
// Invalidate cache for all sites to get the content in the right language.
|
// Invalidate cache for all sites to get the content in the right language.
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
<ion-item *ngFor="let section of sections.items" [attr.aria-current]="sections.getItemAriaCurrent(section)" button
|
<ion-item *ngFor="let section of sections.items" [attr.aria-current]="sections.getItemAriaCurrent(section)" button
|
||||||
[detail]="true" (click)="sections.select(section)">
|
[detail]="true" (click)="sections.select(section)">
|
||||||
<ion-icon [name]="section.icon" slot="start" aria-hidden="true" />
|
<ion-icon [name]="section.icon" slot="start" aria-hidden="true" />
|
||||||
<ion-label>{{ section.name | translate }}</ion-label>
|
<ion-label class="ion-text-wrap">
|
||||||
|
<p class="item-heading">{{ section.name | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</core-split-view>
|
</core-split-view>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="handlers.loaded">
|
<core-loading [hideUntil]="handlers.loaded">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item *ngFor="let handler of handlerItems" class="core-settings-handler ion-no-validation" [ngClass]="handler.class"
|
<ion-item *ngFor="let handler of handlerItems" class="core-settings-handler" lines="none" [ngClass]="handler.class"
|
||||||
[attr.aria-label]="handler.title | translate" (click)="!handler.toggle && handlers.select(handler)"
|
[attr.aria-label]="handler.title | translate" (click)="!handler.toggle && handlers.select(handler)"
|
||||||
[button]="!handler.toggle" [detail]="!handler.toggle" [attr.aria-current]="handlers.getItemAriaCurrent(handler)">
|
[button]="!handler.toggle" [detail]="!handler.toggle" [attr.aria-current]="handlers.getItemAriaCurrent(handler)">
|
||||||
<ion-icon [name]="handler.icon" slot="start" *ngIf="handler.icon" aria-hidden="true" />
|
<ion-icon [name]="handler.icon" slot="start" *ngIf="handler.icon" aria-hidden="true" />
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<h2>{{ 'core.settings.syncsettings' | translate }}</h2>
|
<h2>{{ 'core.settings.syncsettings' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap ion-no-validation">
|
<ion-item class="ion-text-wrap" lines="none">
|
||||||
<ion-toggle [(ngModel)]="dataSaver" (ngModelChange)="syncOnlyOnWifiChanged()">
|
<ion-toggle [(ngModel)]="dataSaver" (ngModelChange)="syncOnlyOnWifiChanged()">
|
||||||
{{ 'core.settings.syncdatasaver' | translate }}
|
{{ 'core.settings.syncdatasaver' | translate }}
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { ModalController, Translate } from '@singletons';
|
||||||
import { CoreMath } from '@singletons/math';
|
import { CoreMath } from '@singletons/math';
|
||||||
import { Swiper } from 'swiper';
|
import { Swiper } from 'swiper';
|
||||||
import { SwiperOptions } from 'swiper/types';
|
import { SwiperOptions } from 'swiper/types';
|
||||||
import { IonicSlides } from '@ionic/angular';
|
import { CoreSwiper } from '@singletons/swiper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modal component to view an image.
|
* Modal component to view an image.
|
||||||
|
@ -30,23 +30,21 @@ import { IonicSlides } from '@ionic/angular';
|
||||||
export class CoreViewerImageComponent implements OnInit {
|
export class CoreViewerImageComponent implements OnInit {
|
||||||
|
|
||||||
protected swiper?: Swiper;
|
protected swiper?: Swiper;
|
||||||
@ViewChild('swiperRef')
|
@ViewChild('swiperRef') set swiperRef(swiperRef: ElementRef) {
|
||||||
set swiperRef(swiperRef: ElementRef) {
|
|
||||||
/**
|
/**
|
||||||
* This setTimeout waits for Ionic's async initialization to complete.
|
* This setTimeout waits for Ionic's async initialization to complete.
|
||||||
* Otherwise, an outdated swiper reference will be used.
|
* Otherwise, an outdated swiper reference will be used.
|
||||||
*/
|
*/
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (swiperRef.nativeElement?.swiper) {
|
const swiper = CoreSwiper.initSwiperIfAvailable(this.swiper, swiperRef, this.swiperOpts);
|
||||||
this.swiper = swiperRef.nativeElement.swiper as Swiper;
|
if (!swiper) {
|
||||||
|
return;
|
||||||
Object.keys(this.swiperOpts).forEach((key) => {
|
|
||||||
if (this.swiper) {
|
|
||||||
this.swiper.params[key] = this.swiperOpts[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, 0);
|
|
||||||
|
this.swiper = swiper;
|
||||||
|
|
||||||
|
this.swiper.zoom.enable();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input() title = ''; // Modal title.
|
@Input() title = ''; // Modal title.
|
||||||
|
@ -55,24 +53,20 @@ export class CoreViewerImageComponent implements OnInit {
|
||||||
@Input() componentId?: string | number; // Component ID to use in external-content.
|
@Input() componentId?: string | number; // Component ID to use in external-content.
|
||||||
|
|
||||||
private static readonly MAX_RATIO = 8;
|
private static readonly MAX_RATIO = 8;
|
||||||
|
private static readonly MIN_RATIO = 0.5;
|
||||||
|
|
||||||
protected swiperOpts: SwiperOptions = {
|
protected swiperOpts: SwiperOptions = {
|
||||||
modules: [IonicSlides],
|
|
||||||
freeMode: true,
|
freeMode: true,
|
||||||
slidesPerView: 1,
|
slidesPerView: 1,
|
||||||
centerInsufficientSlides: true,
|
centerInsufficientSlides: true,
|
||||||
centeredSlides: true,
|
centeredSlides: true,
|
||||||
zoom: {
|
zoom: {
|
||||||
maxRatio: CoreViewerImageComponent.MAX_RATIO,
|
maxRatio: CoreViewerImageComponent.MAX_RATIO,
|
||||||
minRatio: 0.5, // User can zoom out to 0.5 only using pinch gesture.
|
minRatio: CoreViewerImageComponent.MIN_RATIO,
|
||||||
|
toggle: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
protected zoomRatio = 1;
|
|
||||||
|
|
||||||
constructor(protected element: ElementRef<HTMLElement>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -93,27 +87,18 @@ export class CoreViewerImageComponent implements OnInit {
|
||||||
* @param zoomIn True to zoom in, false to zoom out.
|
* @param zoomIn True to zoom in, false to zoom out.
|
||||||
*/
|
*/
|
||||||
zoom(zoomIn = true): void {
|
zoom(zoomIn = true): void {
|
||||||
const imageElement = this.element.nativeElement.querySelector('img');
|
if (!this.swiper) {
|
||||||
|
|
||||||
if (!this.swiper || !imageElement) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let zoomRatio = this.swiper.zoom.scale;
|
||||||
zoomIn
|
zoomIn
|
||||||
? this.zoomRatio *= 2
|
? zoomRatio *= 2
|
||||||
: this.zoomRatio /= 2;
|
: zoomRatio /= 2;
|
||||||
|
|
||||||
// Using 1 as minimum for manual zoom.
|
zoomRatio = CoreMath.clamp(zoomRatio, CoreViewerImageComponent.MIN_RATIO, CoreViewerImageComponent.MAX_RATIO);
|
||||||
this.zoomRatio = CoreMath.clamp(this.zoomRatio, 1, CoreViewerImageComponent.MAX_RATIO);
|
|
||||||
|
|
||||||
if (this.zoomRatio > 1) {
|
this.swiper.zoom.in(zoomRatio);
|
||||||
this.swiper.zoom.in();
|
|
||||||
|
|
||||||
imageElement.style.transform =
|
|
||||||
'translate3d(0px, 0px, 0px) scale(' + this.zoomRatio + ')';
|
|
||||||
} else {
|
|
||||||
this.swiper.zoom.out();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
// (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 { ElementRef } from '@angular/core';
|
||||||
|
import { IonicSlides } from '@ionic/angular';
|
||||||
|
import { CorePlatform } from '@services/platform';
|
||||||
|
import Swiper from 'swiper';
|
||||||
|
import { SwiperOptions } from 'swiper/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton with helper functions for SwiperJS.
|
||||||
|
*/
|
||||||
|
export class CoreSwiper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a Swiper instance.
|
||||||
|
* It will return swiper instance if current is not set or destroyed and new is set and not destroyed.
|
||||||
|
*
|
||||||
|
* @param currentSwiper Current Swiper instance.
|
||||||
|
* @param newSwiperRef New Swiper Element Ref.
|
||||||
|
* @param swiperOpts Swiper options.
|
||||||
|
* @returns Initialized Swiper instance.
|
||||||
|
*/
|
||||||
|
static initSwiperIfAvailable(
|
||||||
|
currentSwiper?: Swiper,
|
||||||
|
newSwiperRef?: ElementRef,
|
||||||
|
swiperOpts?: SwiperOptions,
|
||||||
|
): Swiper | undefined {
|
||||||
|
const swiper = newSwiperRef?.nativeElement?.swiper as Swiper | undefined;
|
||||||
|
if (!swiper || swiper.destroyed || (currentSwiper && !currentSwiper.destroyed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Swiper.use([IonicSlides]);
|
||||||
|
|
||||||
|
CoreSwiper.updateOptions(swiper, swiperOpts);
|
||||||
|
|
||||||
|
swiper.changeLanguageDirection(CorePlatform.isRTL ? 'rtl' : 'ltr');
|
||||||
|
|
||||||
|
return swiper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Swiper options.
|
||||||
|
*
|
||||||
|
* @param swiper Swiper instance.
|
||||||
|
* @param swiperOpts Swiper options.
|
||||||
|
*/
|
||||||
|
static updateOptions(swiper: Swiper, swiperOpts?: SwiperOptions): void {
|
||||||
|
if (!swiperOpts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(swiper.el, swiperOpts);
|
||||||
|
|
||||||
|
swiper.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,15 +13,18 @@ ion-item.item {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.item-lines-default {
|
&.item-lines-default {
|
||||||
|
/** Remove lines by default */
|
||||||
--inner-border-width: 0px;
|
--inner-border-width: 0px;
|
||||||
--border-width: 0px;
|
--border-width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ion-valid,
|
&.ion-valid,
|
||||||
&.ion-invalid {
|
&.ion-invalid {
|
||||||
--inner-border-width: 0 0 1px 0;
|
&.item-lines-default {
|
||||||
|
--border-width: 0 0 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.ion-touched:not(.ion-no-validation) {
|
&.ion-touched {
|
||||||
&.ion-invalid {
|
&.ion-invalid {
|
||||||
--ion-item-border-color: var(--highlight-color-invalid);
|
--ion-item-border-color: var(--highlight-color-invalid);
|
||||||
--highlight-background: var(--ion-item-border-color);
|
--highlight-background: var(--ion-item-border-color);
|
||||||
|
@ -35,10 +38,6 @@ ion-item.item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ion-no-validation {
|
|
||||||
--inner-border-width: 0 0 1px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide details on items to align badges.
|
// Hide details on items to align badges.
|
||||||
&.hide-detail {
|
&.hide-detail {
|
||||||
--detail-icon-opacity: 0;
|
--detail-icon-opacity: 0;
|
||||||
|
@ -185,7 +184,9 @@ ion-item .in-item {
|
||||||
.item > ion-label,
|
.item > ion-label,
|
||||||
.fake-ion-item,
|
.fake-ion-item,
|
||||||
.item.ion-text-wrap > ion-checkbox::part(label),
|
.item.ion-text-wrap > ion-checkbox::part(label),
|
||||||
ion-checkbox.ion-text-wrap::part(label) {
|
ion-checkbox.ion-text-wrap::part(label)
|
||||||
|
.item.ion-text-wrap ion-toggle::part(label),
|
||||||
|
ion-toggle.ion-text-wrap::part(label) {
|
||||||
core-format-text,
|
core-format-text,
|
||||||
core-format-text > *:not(pre) {
|
core-format-text > *:not(pre) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -198,7 +199,8 @@ ion-checkbox.ion-text-wrap::part(label) {
|
||||||
ion-item > .in-item,
|
ion-item > .in-item,
|
||||||
.fake-ion-item.ion-text-wrap,
|
.fake-ion-item.ion-text-wrap,
|
||||||
.item.ion-text-wrap > ion-checkbox::part(label),
|
.item.ion-text-wrap > ion-checkbox::part(label),
|
||||||
ion-checkbox.ion-text-wrap::part(label) {
|
ion-checkbox.ion-text-wrap::part(label),
|
||||||
|
ion-toggle.ion-text-wrap::part(label) {
|
||||||
core-format-text,
|
core-format-text,
|
||||||
core-format-text > *:not(pre) {
|
core-format-text > *:not(pre) {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
@ -210,7 +212,9 @@ ion-checkbox.ion-text-wrap::part(label) {
|
||||||
.item.ion-text-wrap ion-checkbox::part(label),
|
.item.ion-text-wrap ion-checkbox::part(label),
|
||||||
ion-checkbox.ion-text-wrap::part(label),
|
ion-checkbox.ion-text-wrap::part(label),
|
||||||
.item.ion-text-wrap ion-radio::part(label),
|
.item.ion-text-wrap ion-radio::part(label),
|
||||||
ion-radio.ion-text-wrap::part(label) {
|
ion-radio.ion-text-wrap::part(label),
|
||||||
|
.item.ion-text-wrap ion-toggle::part(label),
|
||||||
|
ion-toggle.ion-text-wrap::part(label) {
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue