Merge pull request #3439 from crazyserver/MOBILE-4065

Mobile 4065
main
Dani Palou 2022-11-14 15:33:48 +01:00 committed by GitHub
commit fba9fd8b2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 74 additions and 34 deletions

View File

@ -40,14 +40,14 @@
"addon.block_learningplans.pluginname": "block_lp",
"addon.block_myoverview.all": "block_myoverview",
"addon.block_myoverview.allincludinghidden": "block_myoverview",
"addon.block_myoverview.aria:card": "block_myoverview",
"addon.block_myoverview.aria:list": "block_myoverview",
"addon.block_myoverview.browseallcourses": "local_moodlemobileapp",
"addon.block_myoverview.card": "block_myoverview",
"addon.block_myoverview.favourites": "block_myoverview",
"addon.block_myoverview.future": "block_myoverview",
"addon.block_myoverview.hiddencourses": "block_myoverview",
"addon.block_myoverview.inprogress": "block_myoverview",
"addon.block_myoverview.lastaccessed": "block_myoverview",
"addon.block_myoverview.list": "block_myoverview",
"addon.block_myoverview.nocoursesenrolled": "local_moodlemobileapp",
"addon.block_myoverview.nocoursesenrolleddescription": "local_moodlemobileapp",
"addon.block_myoverview.noresult": "local_moodlemobileapp",

View File

@ -89,11 +89,11 @@
</ion-col>
<ion-col size="auto" *ngIf="isLayoutSwitcherAvailable">
<ion-button *ngIf="layout == 'card'" fill="outline" (click)="toggleLayout('list')"
[attr.aria-label]="'addon.block_myoverview.list' | translate">
[attr.aria-label]="'addon.block_myoverview.aria:list' | translate">
<ion-icon slot="icon-only" name="fas-list" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button *ngIf="layout == 'list'" fill="outline" (click)="toggleLayout('card')"
[attr.aria-label]="'addon.block_myoverview.card' | translate">
[attr.aria-label]="'addon.block_myoverview.aria:card' | translate">
<ion-icon slot="icon-only" name="fas-th" aria-hidden="true"></ion-icon>
</ion-button>
</ion-col>

View File

@ -1,14 +1,14 @@
{
"all": "All",
"allincludinghidden": "All (including removed from view)",
"aria:card": "Switch to card view",
"aria:list": "Switch to list view",
"browseallcourses": "Browse all courses",
"card": "Card",
"favourites": "Starred",
"future": "Future",
"hiddencourses": "Removed from view",
"inprogress": "In progress",
"lastaccessed": "Last accessed",
"list": "List",
"nocoursesenrolled": "You're not enrolled in any courses yet.",
"nocoursesenrolleddescription": "Browse all available courses below and start learning.",
"noresult": "Your search didn't match any courses.",

View File

@ -29,13 +29,13 @@
<ng-container *ngIf="calculatingSize">{{ 'core.calculating' | translate }}</ng-container>
</ion-badge>
</ion-item>
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin"
[disabled]="prefetchCourseData.loading">
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse($event)" expand="block" fill="outline"
class="ion-no-margin" [disabled]="prefetchCourseData.loading">
<ion-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon>
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
{{ prefetchCourseData.statusTranslatable | translate }}
</ion-button>
<ion-button [disabled]="calculatingSize || totalSize <= 0" (click)="deleteForCourse()" expand="block" color="danger"
<ion-button [disabled]="calculatingSize || totalSize <= 0" (click)="deleteForCourse($event)" expand="block" color="danger"
class="ion-no-margin ion-margin-top">
<ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate:
{ name: title }">
@ -90,8 +90,8 @@
{{section.count}} / {{section.total}}
</ion-badge>
</div>
<ion-button (click)="deleteForSection(section)" *ngIf="!section.calculatingSize && section.totalSize > 0"
color="danger" fill="clear">
<ion-button (click)="deleteForSection($event, section)"
*ngIf="!section.calculatingSize && section.totalSize > 0" color="danger" fill="clear">
<ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
</ion-icon>
@ -128,9 +128,9 @@
<core-download-refresh *ngIf="downloadEnabled && module.handlerData?.showDownloadButton &&
module.downloadStatus != statusDownloaded" [status]="module.downloadStatus" [enabled]="true"
[canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner"
(action)="prefetchModule(module, $event)">
(action)="prefetchModule(module)">
</core-download-refresh>
<ion-button fill="clear" (click)="deleteForModule(module, section)"
<ion-button fill="clear" (click)="deleteForModule($event, module, section)"
*ngIf="!module.calculatingSize && module.totalSize > 0" color="danger">
<ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">

View File

@ -352,8 +352,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
* The user has requested a delete for the whole course data.
*
* (This works by deleting data for each module on the course that has data.)
*
* @param event Event object.
*/
async deleteForCourse(): Promise<void> {
async deleteForCourse(event: Event): Promise<void> {
event.stopPropagation();
event.preventDefault();
try {
await CoreDomUtils.showDeleteConfirm(
'addon.storagemanager.confirmdeletedatafrom',
@ -384,9 +389,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
*
* (This works by deleting data for each module in the section that has data.)
*
* @param event Event object.
* @param section Section object with information about section and modules
*/
async deleteForSection(section: AddonStorageManagerCourseSection): Promise<void> {
async deleteForSection(event: Event, section: AddonStorageManagerCourseSection): Promise<void> {
event.stopPropagation();
event.preventDefault();
try {
await CoreDomUtils.showDeleteConfirm(
'addon.storagemanager.confirmdeletedatafrom',
@ -413,10 +422,18 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
/**
* The user has requested a delete for a module's data
*
* @param event Event object.
* @param module Module details
* @param section Section the module belongs to.
*/
async deleteForModule(module: AddonStorageManagerModule, section: AddonStorageManagerCourseSection): Promise<void> {
async deleteForModule(
event: Event,
module: AddonStorageManagerModule,
section: AddonStorageManagerCourseSection,
): Promise<void> {
event.stopPropagation();
event.preventDefault();
if (module.totalSize === 0) {
return;
}
@ -635,8 +652,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
/**
* Prefetch the whole course.
*
* @param event Event object.
*/
async prefetchCourse(): Promise<void> {
async prefetchCourse(event: Event): Promise<void> {
event.stopPropagation();
event.preventDefault();
const courses = await CoreCourses.getUserCourses(true);
let course = courses.find((course) => course.id == this.courseId);
if (!course) {

View File

@ -1,5 +1,5 @@
<ng-content></ng-content>
<ion-button fill="clear" [attr.aria-label]="label | translate" core-suppress-events (onClick)="toggle($event)"
<ion-button fill="clear" [attr.aria-label]="(shown ? 'core.hide' : 'core.show') | translate" core-suppress-events (onClick)="toggle($event)"
(mousedown)="doNotBlur($event)" (keydown)="doNotBlur($event)" (keyup)="toggle($event)">
<ion-icon [name]="shown ? 'fas-eye-slash' : 'fas-eye'" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -45,7 +45,6 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
@ContentChild(IonInput) ionInput?: IonInput;
shown = false; // Whether the password is shown.
label = ''; // Label for the button to show/hide.
protected input?: HTMLInputElement; // Input affected.
protected element: HTMLElement; // Current element.
@ -97,7 +96,6 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
* @param input The input element.
*/
protected setData(input: HTMLInputElement): void {
this.label = this.shown ? 'core.hide' : 'core.show';
input.type = this.shown ? 'text' : 'password';
}

View File

@ -29,10 +29,10 @@
[class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible"
[class.item-hightlighted]="section.highlighted" detail="false" sticky="true">
<ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start"
class="expandable-status-icon" (click)="toggleExpand($event, section)"
class="expandable-status-icon" (ariaButtonClick)="toggleExpand($event, section)"
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"
[class.expandable-status-icon-expanded]="section.expanded">
[class.expandable-status-icon-expanded]="section.expanded" tabindex="0">
</ion-icon>
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon>

View File

@ -2,7 +2,7 @@
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance">
</core-mod-icon>
<ion-label>
<p class="core-modulename" *ngIf="moduleNameTranslated">{{moduleNameTranslated}}</p>
<div class="core-modulename" *ngIf="moduleNameTranslated">{{moduleNameTranslated}}</div>
<h1>
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"
[contextInstanceId]="module.id" [courseId]="courseId">

View File

@ -71,9 +71,9 @@
<ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false">
<ion-label>
<p class="item-heading">
<h2 class="item-heading">
{{'core.course.coursesummary' | translate}}
</p>
</h2>
<core-format-text [text]="course.summary" collapsible-item contextLevel="course" [contextInstanceId]="course.id">
</core-format-text>
</ion-label>
@ -86,9 +86,9 @@
class="expandable-status-icon" [class.expandable-status-icon-expanded]="contactsExpanded">
</ion-icon>
<ion-label>
<p class="item-heading">
<h2 class="item-heading">
{{ 'core.teachers' | translate }}
</p>
</h2>
</ion-label>
</ion-item>
<ng-container *ngIf="contactsExpanded || course.contacts.length < 5">

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { AbstractType, Component, CUSTOM_ELEMENTS_SCHEMA, Type, ViewChild } from '@angular/core';
import { AbstractType, Component, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, Type, ViewChild } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, Subject } from 'rxjs';
import { Observable, of, Subject } from 'rxjs';
import { sep } from 'path';
import { CORE_SITE_SCHEMAS } from '@services/sites';
@ -33,7 +33,7 @@ import { TranslateService, TranslateStore } from '@ngx-translate/core';
import { CoreIonLoadingElement } from '@classes/ion-loading';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DefaultUrlSerializer, UrlSerializer } from '@angular/router';
import { CoreUtils } from '@services/utils/utils';
import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils';
abstract class WrapperComponent<U> {
@ -45,8 +45,14 @@ type ServiceInjectionToken = AbstractType<unknown> | Type<unknown> | string;
let testBedInitialized = false;
const textUtils = new CoreTextUtilsProvider();
const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, Record<string, unknown>][] = [
[Translate, mock({ instant: key => key })],
const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, unknown][] = [
[Translate, mock({
instant: key => key,
get: key => of(key),
onTranslationChange: new EventEmitter(),
onLangChange: new EventEmitter(),
onDefaultLangChange: new EventEmitter(),
})],
[CoreDB, mock({ getDB: () => mock() })],
[CoreNavigator, mock({ navigateToSitePath: () => Promise.resolve(true) })],
[ApplicationInit, mock({
@ -66,7 +72,7 @@ const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, Record<string, unkno
[CoreDomUtils, mock({
showModalLoading: () => Promise.resolve(mock<CoreIonLoadingElement>({ dismiss: jest.fn() })),
})],
[CoreUtils, mock({
[CoreUtils, mock(new CoreUtilsProvider(), {
nextTick: () => Promise.resolve(),
})],
];
@ -273,8 +279,17 @@ export function mockSingleton<T>(
const methods = Array.isArray(methodsOrProperties) ? methodsOrProperties : [];
const instance = getServiceInstance(singleton.injectionToken) as T;
const mockInstance = mock(instance, methods);
const mockInstancePrototype = Object.getPrototypeOf(mockInstance);
Object.assign(mockInstance as Record<string, unknown>, properties);
for (const [name, value] of Object.entries(properties)) {
const descriptor = Object.getOwnPropertyDescriptor(mockInstancePrototype, name);
if (descriptor && !descriptor.writable) {
continue;
}
mockInstance[name] = value;
}
singleton.setInstance(mockInstance);

View File

@ -1568,6 +1568,11 @@ ion-modal:focus-visible {
box-shadow: none;
}
input {
--placeholder-color: var(--ion-placeholder-color);
--placeholder-opacity: .85;
}
ion-input .native-input {
&:focus, &:focus-visible {
box-shadow: none;