commit
5e085f69b1
|
@ -2380,18 +2380,18 @@
|
|||
}
|
||||
},
|
||||
"@ionic/angular": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.3.4.tgz",
|
||||
"integrity": "sha512-eqw03rWvQKM0pcuxPxQvXeCUGhcaQCZ+yqQRp9A6cBDE6u6JJS2tGs6OyZ07hhyDF8IJ5BLpJwjiRjEDnhyTDQ==",
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.5.2.tgz",
|
||||
"integrity": "sha512-yXIydPTIMAX4RobidAByaQ/y+yMS6FYgwEs08GTN/GyvQ4XeWVbojwTm62ILLN2qYS/80ok2uupFwlcyKSMztw==",
|
||||
"requires": {
|
||||
"@ionic/core": "5.3.4",
|
||||
"@ionic/core": "5.5.2",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2424,18 +2424,18 @@
|
|||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.3.4.tgz",
|
||||
"integrity": "sha512-4UVzj+Vd7o0VJ06dReG01PvttnLLPSzUVgXSYMBKKR849Pvuh5Q9t5s4GEEQgGoxhv1S6Ai+zphWGFMvviOyfw==",
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.5.2.tgz",
|
||||
"integrity": "sha512-rOfPj8D5NRWdOYYulNTdKtMAMURfmutDQ3ciA3L7daCooG3MWt2/0siiL6rcZFMxfG7KDxHctuwVwYoC1mPuhg==",
|
||||
"requires": {
|
||||
"ionicons": "^5.1.2",
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -11042,9 +11042,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ionicons": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.1.2.tgz",
|
||||
"integrity": "sha512-zO7ZgbBbXhpA7cXO2rDzTNdcCqErjg1Sprq/ossTvaiV0MriOjRE7JO3EGvYjDTPzF9YALGpvLXqCgsRT0tprA=="
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.2.3.tgz",
|
||||
"integrity": "sha512-87qtgBkieKVFagwYA9Cf91B3PCahQbEOMwMt8bSvlQSgflZ4eE5qI4MGj2ZlIyadeX0dgo+0CzZsy3ow0CsBAg=="
|
||||
},
|
||||
"ios-sim": {
|
||||
"version": "8.0.2",
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
"@ionic-native/status-bar": "^5.0.0",
|
||||
"@ionic-native/web-intent": "^5.28.0",
|
||||
"@ionic-native/zip": "^5.28.0",
|
||||
"@ionic/angular": "^5.0.0",
|
||||
"@ionic/angular": "^5.5.2",
|
||||
"@ngx-translate/core": "^13.0.0",
|
||||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"@types/cordova": "0.0.34",
|
||||
|
|
|
@ -16,11 +16,13 @@ import { NgModule } from '@angular/core';
|
|||
|
||||
import { AddonPrivateFilesModule } from './privatefiles/privatefiles.module';
|
||||
import { AddonFilterModule } from './filter/filter.module';
|
||||
import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AddonPrivateFilesModule,
|
||||
AddonFilterModule,
|
||||
AddonUserProfileFieldModule,
|
||||
],
|
||||
})
|
||||
export class AddonsModule {}
|
||||
|
|
|
@ -12,23 +12,33 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Injector, NgModule } from '@angular/core';
|
||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'root', // Fake "hash".
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: ':hash',
|
||||
loadChildren: () => import('./pages/index/index.module').then(m => m.AddonPrivateFilesIndexPageModule),
|
||||
},
|
||||
];
|
||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
return [
|
||||
{
|
||||
path: ':hash',
|
||||
loadChildren: () => import('./pages/index/index.module').then(m => m.AddonPrivateFilesIndexPageModule),
|
||||
},
|
||||
...buildTabMainRoutes(injector, {
|
||||
redirectTo: 'root', // Fake "hash".
|
||||
pathMatch: 'full',
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ROUTES,
|
||||
multi: true,
|
||||
deps: [Injector],
|
||||
useFactory: buildRoutes,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonPrivateFilesLazyModule {}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonUserProfileFieldCheckboxHandler } from './services/handlers/checkbox';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldCheckboxComponent } from './component/checkbox';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldCheckboxComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () =>
|
||||
CoreUserProfileFieldDelegate.instance.registerHandler(AddonUserProfileFieldCheckboxHandler.instance),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldCheckboxComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldCheckboxComponent,
|
||||
],
|
||||
})
|
||||
export class AddonUserProfileFieldCheckboxModule {}
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<ion-label>
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p *ngIf="value != '0'">
|
||||
{{ 'core.yes' | translate }}
|
||||
</p>
|
||||
<p *ngIf="value == '0'">
|
||||
{{ 'core.no' | translate }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" [formGroup]="form">
|
||||
<ion-label>
|
||||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
<core-input-errors [control]="form.controls[modelName]"></core-input-errors>
|
||||
</ion-label>
|
||||
<ion-checkbox item-end [formControlName]="modelName">
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
|
@ -0,0 +1,45 @@
|
|||
// (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 { Component } from '@angular/core';
|
||||
import { Validators, FormControl } from '@angular/forms';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a checkbox user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-checkbox',
|
||||
templateUrl: 'addon-user-profile-field-checkbox.html',
|
||||
})
|
||||
export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent {
|
||||
|
||||
/**
|
||||
* Create the Form control.
|
||||
*
|
||||
* @return Form control.
|
||||
*/
|
||||
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
|
||||
const formData = {
|
||||
value: CoreUtils.instance.isTrueOrOne(field.defaultdata),
|
||||
disabled: this.disabled,
|
||||
};
|
||||
|
||||
return new FormControl(formData, this.required && !field.locked ? Validators.requiredTrue : null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonUserProfileFieldCheckboxComponent } from '../../component/checkbox';
|
||||
|
||||
/**
|
||||
* Checkbox user profile field handlers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonUserProfileFieldCheckboxHandlerService implements CoreUserProfileFieldHandler {
|
||||
|
||||
name = 'AddonUserProfileFieldCheckbox';
|
||||
type = 'checkbox';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getData(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (typeof formValues[name] != 'undefined') {
|
||||
return {
|
||||
type: 'checkbox',
|
||||
name: name,
|
||||
value: formValues[name] ? 1 : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return AddonUserProfileFieldCheckboxComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonUserProfileFieldCheckboxHandler extends makeSingleton(AddonUserProfileFieldCheckboxHandlerService) {}
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<ion-label>
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p>{{ valueNumber * 1000 | coreFormatDate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" text-wrap [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
</ion-label>
|
||||
<ion-datetime [formControlName]="modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="format"
|
||||
[max]="max" [min]="min">
|
||||
</ion-datetime>
|
||||
<core-input-errors [control]="form.controls[modelName]"></core-input-errors>
|
||||
</ion-item>
|
|
@ -0,0 +1,95 @@
|
|||
// (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 { FormControl, Validators } from '@angular/forms';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
|
||||
|
||||
/**
|
||||
* Directive to render a datetime user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-datetime',
|
||||
templateUrl: 'addon-user-profile-field-datetime.html',
|
||||
})
|
||||
export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent {
|
||||
|
||||
format?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
valueNumber = 0;
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed without editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForNonEdit(field: CoreUserProfileField): void {
|
||||
this.valueNumber = Number(field.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed for editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForEdit(field: AuthEmailSignupProfileField): void {
|
||||
super.initForEdit(field);
|
||||
|
||||
// Check if it's only date or it has time too.
|
||||
const hasTime = CoreUtils.instance.isTrueOrOne(field.param3);
|
||||
|
||||
// Calculate format to use.
|
||||
this.format = CoreTimeUtils.instance.fixFormatForDatetime(CoreTimeUtils.instance.convertPHPToMoment(
|
||||
Translate.instance.instant('core.' + (hasTime ? 'strftimedatetime' : 'strftimedate')),
|
||||
));
|
||||
|
||||
// Check min value.
|
||||
if (field.param1) {
|
||||
const year = parseInt(field.param1, 10);
|
||||
if (year) {
|
||||
this.min = year;
|
||||
}
|
||||
}
|
||||
|
||||
// Check max value.
|
||||
if (field.param2) {
|
||||
const year = parseInt(field.param2, 10);
|
||||
if (year) {
|
||||
this.max = year;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Form control.
|
||||
*
|
||||
* @return Form control.
|
||||
*/
|
||||
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
|
||||
const formData = {
|
||||
value: field.defaultdata != '0' ? field.defaultdata : undefined,
|
||||
disabled: this.disabled,
|
||||
};
|
||||
|
||||
return new FormControl(formData, this.required && !field.locked ? Validators.required : null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonUserProfileFieldDatetimeHandler } from './services/handlers/datetime';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldDatetimeComponent } from './component/datetime';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldDatetimeComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
CorePipesModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () =>
|
||||
CoreUserProfileFieldDelegate.instance.registerHandler(AddonUserProfileFieldDatetimeHandler.instance),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldDatetimeComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldDatetimeComponent,
|
||||
],
|
||||
})
|
||||
export class AddonUserProfileFieldDatetimeModule {}
|
|
@ -0,0 +1,81 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonUserProfileFieldDatetimeComponent } from '../../component/datetime';
|
||||
|
||||
/**
|
||||
* Datetime user profile field handlers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonUserProfileFieldDatetimeHandlerService implements CoreUserProfileFieldHandler {
|
||||
|
||||
name = 'AddonUserProfileFieldDatetime';
|
||||
type = 'datetime';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getData(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
return {
|
||||
type: 'datetime',
|
||||
name: 'profile_field_' + field.shortname,
|
||||
value: CoreTimeUtils.instance.convertToTimestamp(<string> formValues[name]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return AddonUserProfileFieldDatetimeComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonUserProfileFieldDatetimeHandler extends makeSingleton(AddonUserProfileFieldDatetimeHandlerService) {}
|
|
@ -0,0 +1,21 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<ion-label>
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="value" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" text-wrap [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
</ion-label>
|
||||
<ion-select [formControlName]="modelName" [placeholder]="'core.choosedots' | translate" interface="action-sheet">
|
||||
<ion-select-option value="">{{ 'core.choosedots' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">{{option}}</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors [control]="form.controls[modelName]"></core-input-errors>
|
||||
</ion-item>
|
|
@ -0,0 +1,47 @@
|
|||
// (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 { Component } from '@angular/core';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
|
||||
|
||||
/**
|
||||
* Directive to render a menu user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-menu',
|
||||
templateUrl: 'addon-user-profile-field-menu.html',
|
||||
})
|
||||
export class AddonUserProfileFieldMenuComponent extends CoreUserProfileFieldBaseComponent {
|
||||
|
||||
options?: string[];
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed for editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForEdit(field: AuthEmailSignupProfileField): void {
|
||||
super.initForEdit(field);
|
||||
|
||||
// Parse options.
|
||||
if (field.param1) {
|
||||
this.options = field.param1.split(/\r\n|\r|\n/g);
|
||||
} else {
|
||||
this.options = [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonUserProfileFieldMenuHandler } from './services/handlers/menu';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldMenuComponent } from './component/menu';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldMenuComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () =>
|
||||
CoreUserProfileFieldDelegate.instance.registerHandler(AddonUserProfileFieldMenuHandler.instance),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldMenuComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldMenuComponent,
|
||||
],
|
||||
})
|
||||
export class AddonUserProfileFieldMenuModule {}
|
|
@ -0,0 +1,79 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonUserProfileFieldMenuComponent } from '../../component/menu';
|
||||
|
||||
/**
|
||||
* Menu user profile field handlers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonUserProfileFieldMenuHandlerService implements CoreUserProfileFieldHandler {
|
||||
|
||||
name = 'AddonUserProfileFieldMenu';
|
||||
type = 'menu';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getData(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
return {
|
||||
type: 'menu',
|
||||
name: name,
|
||||
value: formValues[name],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return AddonUserProfileFieldMenuComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonUserProfileFieldMenuHandler extends makeSingleton(AddonUserProfileFieldMenuHandlerService) {}
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<ion-label>
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="value" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" text-wrap [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
</ion-label>
|
||||
<ion-input [type]="inputType" [formControlName]="modelName" [placeholder]="field.name" maxlength="{{maxLength}}"></ion-input>
|
||||
<core-input-errors [control]="form.controls[modelName]"></core-input-errors>
|
||||
</ion-item>
|
|
@ -0,0 +1,50 @@
|
|||
// (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 { Component } from '@angular/core';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a text user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-text',
|
||||
templateUrl: 'addon-user-profile-field-text.html',
|
||||
})
|
||||
export class AddonUserProfileFieldTextComponent extends CoreUserProfileFieldBaseComponent {
|
||||
|
||||
inputType?: string;
|
||||
maxLength?: number;
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed for editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForEdit(field: AuthEmailSignupProfileField): void {
|
||||
super.initForEdit(field);
|
||||
|
||||
// Check max length.
|
||||
if (field.param2) {
|
||||
this.maxLength = parseInt(field.param2, 10) || Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
// Check if it's a password or text.
|
||||
this.inputType = CoreUtils.instance.isTrueOrOne(field.param3) ? 'password' : 'text';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from '../../component/text';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Text user profile field handlers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonUserProfileFieldTextHandlerService implements CoreUserProfileFieldHandler {
|
||||
|
||||
name = 'AddonUserProfileFieldText';
|
||||
type = 'text';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return True or promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getData(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
name: name,
|
||||
value: CoreTextUtils.instance.cleanTags(<string> formValues[name]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return AddonUserProfileFieldTextComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonUserProfileFieldTextHandler extends makeSingleton(AddonUserProfileFieldTextHandlerService) {}
|
|
@ -0,0 +1,56 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonUserProfileFieldTextHandler } from './services/handlers/text';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from './component/text';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldTextComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () =>
|
||||
CoreUserProfileFieldDelegate.instance.registerHandler(AddonUserProfileFieldTextHandler.instance),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldTextComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldTextComponent,
|
||||
],
|
||||
})
|
||||
export class AddonUserProfileFieldTextModule {}
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<ion-label>
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="value" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
<core-input-errors [control]="control"></core-input-errors>
|
||||
</ion-label>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="field.name" [autoSave]="true"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [elementId]="modelName">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
|
@ -0,0 +1,26 @@
|
|||
// (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 { Component } from '@angular/core';
|
||||
|
||||
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
|
||||
|
||||
/**
|
||||
* Directive to render a textarea user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-textarea',
|
||||
templateUrl: 'addon-user-profile-field-textarea.html',
|
||||
})
|
||||
export class AddonUserProfileFieldTextareaComponent extends CoreUserProfileFieldBaseComponent {}
|
|
@ -0,0 +1,88 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from '../../component/textarea';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Textarea user profile field handlers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonUserProfileFieldTextareaHandlerService implements CoreUserProfileFieldHandler {
|
||||
|
||||
name = 'AddonUserProfileFieldTextarea';
|
||||
type = 'textarea';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return True or promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getData(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
let text = <string> formValues[name] || '';
|
||||
// Add some HTML to the message in case the user edited with textarea.
|
||||
text = CoreTextUtils.instance.formatHtmlLines(text);
|
||||
|
||||
return {
|
||||
type: 'textarea',
|
||||
name: name,
|
||||
value: JSON.stringify({
|
||||
text: text,
|
||||
format: 1, // Always send this format.
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return AddonUserProfileFieldTextareaComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonUserProfileFieldTextareaHandler extends makeSingleton(AddonUserProfileFieldTextareaHandlerService) {}
|
|
@ -0,0 +1,58 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonUserProfileFieldTextareaHandler } from './services/handlers/textarea';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from './component/textarea';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldTextareaComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule.forRoot(),
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () =>
|
||||
CoreUserProfileFieldDelegate.instance.registerHandler(AddonUserProfileFieldTextareaHandler.instance),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldTextareaComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldTextareaComponent,
|
||||
],
|
||||
})
|
||||
export class AddonUserProfileFieldTextareaModule {}
|
|
@ -0,0 +1,33 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
|
||||
import { AddonUserProfileFieldCheckboxModule } from './checkbox/checkbox.module';
|
||||
import { AddonUserProfileFieldDatetimeModule } from './datetime/datetime.module';
|
||||
import { AddonUserProfileFieldMenuModule } from './menu/menu.module';
|
||||
import { AddonUserProfileFieldTextModule } from './text/text.module';
|
||||
import { AddonUserProfileFieldTextareaModule } from './textarea/textarea.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
AddonUserProfileFieldCheckboxModule,
|
||||
AddonUserProfileFieldDatetimeModule,
|
||||
AddonUserProfileFieldMenuModule,
|
||||
AddonUserProfileFieldTextModule,
|
||||
AddonUserProfileFieldTextareaModule,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class AddonUserProfileFieldModule { }
|
|
@ -0,0 +1,307 @@
|
|||
// (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 { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Blocked sync error.
|
||||
*/
|
||||
export class CoreSyncBlockedError extends CoreError {}
|
||||
|
||||
/**
|
||||
* Base class to create sync providers. It provides some common functions.
|
||||
*/
|
||||
export class CoreSyncBaseProvider<T = void> {
|
||||
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
protected logger: CoreLogger;
|
||||
|
||||
/**
|
||||
* Component of the sync provider.
|
||||
*/
|
||||
component = 'core';
|
||||
|
||||
/**
|
||||
* Sync provider's interval.
|
||||
*/
|
||||
syncInterval = 300000;
|
||||
|
||||
// Store sync promises.
|
||||
protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<T> } } = {};
|
||||
|
||||
constructor(component: string) {
|
||||
this.logger = CoreLogger.getInstance(component);
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an offline data deleted warning to a list of warnings.
|
||||
*
|
||||
* @param warnings List of warnings.
|
||||
* @param component Component.
|
||||
* @param name Instance name.
|
||||
* @param error Specific error message.
|
||||
*/
|
||||
protected addOfflineDataDeletedWarning(warnings: string[], component: string, name: string, error: string): void {
|
||||
const warning = Translate.instance.instant('core.warningofflinedatadeleted', {
|
||||
component: component,
|
||||
name: name,
|
||||
error: error,
|
||||
});
|
||||
|
||||
if (warnings.indexOf(warning) == -1) {
|
||||
warnings.push(warning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an ongoing sync to the syncPromises list. On finish the promise will be removed.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param promise The promise of the sync to add.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return The sync promise.
|
||||
*/
|
||||
async addOngoingSync(id: string | number, promise: Promise<T>, siteId?: string): Promise<T> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
if (!siteId) {
|
||||
throw new CoreError('CoreSyncBaseProvider: Site ID not supplied');
|
||||
}
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
if (!this.syncPromises[siteId]) {
|
||||
this.syncPromises[siteId] = {};
|
||||
}
|
||||
|
||||
this.syncPromises[siteId][uniqueId] = promise;
|
||||
|
||||
// Promise will be deleted when finish.
|
||||
try {
|
||||
return await promise;
|
||||
} finally {
|
||||
delete this.syncPromises[siteId!][uniqueId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's an ongoing sync for a certain identifier return it.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise of the current sync or undefined if there isn't any.
|
||||
*/
|
||||
getOngoingSync(id: string | number, siteId?: string): Promise<T> | undefined {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
if (!this.isSyncing(id, siteId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There's already a sync ongoing for this id, return the promise.
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
||||
return this.syncPromises[siteId][uniqueId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synchronization time in a human readable format.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the readable time.
|
||||
*/
|
||||
async getReadableSyncTime(id: string | number, siteId?: string): Promise<string> {
|
||||
const time = await this.getSyncTime(id, siteId);
|
||||
|
||||
return this.getReadableTimeFromTimestamp(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a timestamp return it in a human readable format.
|
||||
*
|
||||
* @param timestamp Timestamp
|
||||
* @return Human readable time.
|
||||
*/
|
||||
getReadableTimeFromTimestamp(timestamp: number): string {
|
||||
if (!timestamp) {
|
||||
return Translate.instance.instant('core.never');
|
||||
} else {
|
||||
return CoreTimeUtils.instance.userDate(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synchronization time. Returns 0 if no time stored.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the time.
|
||||
*/
|
||||
async getSyncTime(id: string | number, siteId?: string): Promise<number> {
|
||||
try {
|
||||
const entry = await CoreSync.instance.getSyncRecord(this.component, id, siteId);
|
||||
|
||||
return entry.time;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synchronization warnings of an instance.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the warnings.
|
||||
*/
|
||||
async getSyncWarnings(id: string | number, siteId?: string): Promise<string[]> {
|
||||
try {
|
||||
const entry = await CoreSync.instance.getSyncRecord(this.component, id, siteId);
|
||||
|
||||
return <string[]> CoreTextUtils.instance.parseJSON(entry.warnings, []);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique identifier from component and id.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @return Unique identifier from component and id.
|
||||
*/
|
||||
protected getUniqueSyncId(id: string | number): string {
|
||||
return this.component + '#' + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a there's an ongoing syncronization for the given id.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Whether it's synchronizing.
|
||||
*/
|
||||
isSyncing(id: string | number, siteId?: string): boolean {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
||||
return !!(this.syncPromises[siteId] && this.syncPromises[siteId][uniqueId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a sync is needed: if a certain time has passed since the last time.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with boolean: whether sync is needed.
|
||||
*/
|
||||
async isSyncNeeded(id: string | number, siteId?: string): Promise<boolean> {
|
||||
const time = await this.getSyncTime(id, siteId);
|
||||
|
||||
return Date.now() - this.syncInterval >= time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronization time.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param time Time to set. If not defined, current time.
|
||||
* @return Promise resolved when the time is set.
|
||||
*/
|
||||
async setSyncTime(id: string, siteId?: string, time?: number): Promise<void> {
|
||||
time = typeof time != 'undefined' ? time : Date.now();
|
||||
|
||||
await CoreSync.instance.insertOrUpdateSyncRecord(this.component, id, { time: time }, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronization warnings.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param warnings Warnings to set.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async setSyncWarnings(id: string, warnings: string[], siteId?: string): Promise<void> {
|
||||
const warningsText = JSON.stringify(warnings || []);
|
||||
|
||||
await CoreSync.instance.insertOrUpdateSyncRecord(this.component, id, { warnings: warningsText }, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a sync function on selected sites.
|
||||
*
|
||||
* @param syncFunctionLog Log message to explain the sync function purpose.
|
||||
* @param syncFunction Sync function to execute.
|
||||
* @param siteId Site ID to sync. If not defined, sync all sites.
|
||||
* @return Resolved with siteIds selected. Rejected if offline.
|
||||
*/
|
||||
async syncOnSites(syncFunctionLog: string, syncFunction: (siteId: string) => void, siteId?: string): Promise<void> {
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
const message = `Cannot sync '${syncFunctionLog}' because device is offline.`;
|
||||
this.logger.debug(message);
|
||||
|
||||
throw new CoreError(message);
|
||||
}
|
||||
|
||||
let siteIds: string[] = [];
|
||||
|
||||
if (!siteId) {
|
||||
// No site ID defined, sync all sites.
|
||||
this.logger.debug(`Try to sync '${syncFunctionLog}' in all sites.`);
|
||||
siteIds = await CoreSites.instance.getLoggedInSitesIds();
|
||||
} else {
|
||||
this.logger.debug(`Try to sync '${syncFunctionLog}' in site '${siteId}'.`);
|
||||
siteIds = [siteId];
|
||||
}
|
||||
|
||||
// Execute function for every site.
|
||||
await Promise.all(siteIds.map((siteId) => syncFunction(siteId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's an ongoing sync for a certain identifier, wait for it to end.
|
||||
* If there's no sync ongoing the promise will be resolved right away.
|
||||
*
|
||||
* @param id Unique sync identifier per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when there's no sync going on for the identifier.
|
||||
*/
|
||||
async waitForSync(id: string | number, siteId?: string): Promise<T | undefined> {
|
||||
const promise = this.getOngoingSync(id, siteId);
|
||||
|
||||
if (!promise) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -86,7 +86,7 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
|
|||
* @param delegateName Delegate name used for logging purposes.
|
||||
* @param listenSiteEvents Whether to update the handler when a site event occurs (login, site updated, ...).
|
||||
*/
|
||||
constructor(delegateName: string, listenSiteEvents?: boolean) {
|
||||
constructor(delegateName: string, listenSiteEvents: boolean = true) {
|
||||
this.logger = CoreLogger.getInstance(delegateName);
|
||||
|
||||
this.handlersInitPromise = new Promise((resolve): void => {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { Md5 } from 'ts-md5/dist/md5';
|
|||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreDB } from '@services/db';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreEvents, CoreEventUserDeletedData } from '@singletons/events';
|
||||
import { CoreFile } from '@services/file';
|
||||
import {
|
||||
CoreWS,
|
||||
|
@ -588,7 +588,7 @@ export class CoreSite {
|
|||
error.message = Translate.instance.instant('core.lostconnection');
|
||||
} else if (error.errorcode === 'userdeleted') {
|
||||
// User deleted, trigger event.
|
||||
CoreEvents.trigger(CoreEvents.USER_DELETED, { params: data }, this.id);
|
||||
CoreEvents.trigger<CoreEventUserDeletedData>(CoreEvents.USER_DELETED, { params: data }, this.id);
|
||||
error.message = Translate.instance.instant('core.userdeleted');
|
||||
|
||||
throw new CoreWSError(error);
|
||||
|
|
|
@ -35,6 +35,8 @@ import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
|||
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||
import { CoreUserAvatarComponent } from './user-avatar/user-avatar';
|
||||
import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
||||
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
|
@ -61,6 +63,8 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
|||
CoreContextMenuItemComponent,
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreUserAvatarComponent,
|
||||
CoreDynamicComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -89,6 +93,8 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
|||
CoreContextMenuItemComponent,
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreUserAvatarComponent,
|
||||
CoreDynamicComponent,
|
||||
],
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<!-- Content to display if no dynamic component. -->
|
||||
<ng-content *ngIf="!instance"></ng-content>
|
||||
|
||||
<!-- Container of the dynamic component -->
|
||||
<ng-container #dynamicComponent></ng-container>
|
|
@ -0,0 +1,200 @@
|
|||
// (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 {
|
||||
Component,
|
||||
Input,
|
||||
ViewChild,
|
||||
OnChanges,
|
||||
DoCheck,
|
||||
ViewContainerRef,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
KeyValueDiffers,
|
||||
SimpleChange,
|
||||
ChangeDetectorRef,
|
||||
Optional,
|
||||
ElementRef,
|
||||
KeyValueDiffer,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
import { NavController } from '@ionic/angular';
|
||||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
* Component to create another component dynamically.
|
||||
*
|
||||
* You need to pass the class of the component to this component (the class, not the name), along with the input data.
|
||||
*
|
||||
* So you should do something like:
|
||||
*
|
||||
* import { MyComponent } from './component';
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* this.component = MyComponent;
|
||||
*
|
||||
* And in the template:
|
||||
*
|
||||
* <core-dynamic-component [component]="component" [data]="data">
|
||||
* <p>Cannot render the data.</p>
|
||||
* </core-dynamic-component>
|
||||
*
|
||||
* Please notice that the component that you pass needs to be declared in entryComponents of the module to be created dynamically.
|
||||
*
|
||||
* Alternatively, you can also supply a ComponentRef instead of the class of the component. In this case, the component won't
|
||||
* be instantiated because it already is, it will be attached to the view and the right data will be passed to it.
|
||||
* Passing ComponentRef is meant for site plugins, so we'll inject a NavController instance to the component.
|
||||
*
|
||||
* The contents of this component will be displayed if no component is supplied or it cannot be created. In the example above,
|
||||
* if no component is supplied then the template will show the message "Cannot render the data.".
|
||||
*/
|
||||
/* eslint-disable @angular-eslint/no-conflicting-lifecycle */
|
||||
@Component({
|
||||
selector: 'core-dynamic-component',
|
||||
templateUrl: 'core-dynamic-component.html',
|
||||
})
|
||||
export class CoreDynamicComponent implements OnChanges, DoCheck {
|
||||
|
||||
@Input() component?: Type<unknown>;
|
||||
@Input() data?: Record<string | number, unknown>;
|
||||
|
||||
// Get the container where to put the dynamic component.
|
||||
@ViewChild('dynamicComponent', { read: ViewContainerRef }) set dynamicComponent(el: ViewContainerRef) {
|
||||
this.container = el;
|
||||
|
||||
// Use a timeout to avoid ExpressionChangedAfterItHasBeenCheckedError.
|
||||
setTimeout(() => this.createComponent());
|
||||
}
|
||||
|
||||
instance?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
container?: ViewContainerRef;
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
|
||||
protected lastComponent?: Type<unknown>;
|
||||
|
||||
constructor(
|
||||
protected factoryResolver: ComponentFactoryResolver,
|
||||
differs: KeyValueDiffers,
|
||||
@Optional() protected navCtrl: NavController,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
protected element: ElementRef,
|
||||
) {
|
||||
|
||||
this.logger = CoreLogger.getInstance('CoreDynamicComponent');
|
||||
this.differ = differs.find([]).create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.component && !this.component) {
|
||||
// Component not set, destroy the instance if any.
|
||||
this.lastComponent = undefined;
|
||||
this.instance = undefined;
|
||||
this.container?.clear();
|
||||
} else if (changes.component && (!this.instance || this.component != this.lastComponent)) {
|
||||
this.createComponent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
|
||||
*/
|
||||
ngDoCheck(): void {
|
||||
if (this.instance) {
|
||||
// Check if there's any change in the data object.
|
||||
const changes = this.differ.diff(this.data || {});
|
||||
if (changes) {
|
||||
this.setInputData();
|
||||
if (this.instance.ngOnChanges) {
|
||||
this.instance.ngOnChanges(CoreDomUtils.instance.createChangesFromKeyValueDiff(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a certain function on the component.
|
||||
*
|
||||
* @param name Name of the function to call.
|
||||
* @param params List of params to send to the function.
|
||||
* @return Result of the call. Undefined if no component instance or the function doesn't exist.
|
||||
*/
|
||||
callComponentFunction<T = unknown>(name: string, params?: unknown[]): T | undefined {
|
||||
if (this.instance && typeof this.instance[name] == 'function') {
|
||||
return this.instance[name].apply(this.instance, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a component, add it to a container and set the input data.
|
||||
*
|
||||
* @return Whether the component was successfully created.
|
||||
*/
|
||||
protected createComponent(): boolean {
|
||||
this.lastComponent = this.component;
|
||||
|
||||
if (!this.component || !this.container) {
|
||||
// No component to instantiate or container doesn't exist right now.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.instance) {
|
||||
// Component already instantiated.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.component instanceof ComponentRef) {
|
||||
// A ComponentRef was supplied instead of the component class. Add it to the view.
|
||||
this.container.insert(this.component.hostView);
|
||||
this.instance = this.component.instance;
|
||||
|
||||
// This feature is usually meant for site plugins. Inject some properties.
|
||||
this.instance['ChangeDetectorRef'] = this.cdr;
|
||||
this.instance['NavController'] = this.navCtrl;
|
||||
this.instance['componentContainer'] = this.element.nativeElement;
|
||||
} else {
|
||||
try {
|
||||
// Create the component and add it to the container.
|
||||
const factory = this.factoryResolver.resolveComponentFactory(this.component);
|
||||
const componentRef = this.container.createComponent(factory);
|
||||
|
||||
this.instance = componentRef.instance;
|
||||
} catch (ex) {
|
||||
this.logger.error('Error creating component', ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.setInputData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input data for the component.
|
||||
*/
|
||||
protected setInputData(): void {
|
||||
for (const name in this.data) {
|
||||
this.instance[name] = this.data[name];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<ion-icon *ngIf="showPrevButton" name="fas-chevron-left"></ion-icon>
|
||||
</ion-col>
|
||||
<ion-col class="ion-no-padding" size="10">
|
||||
<ion-slides (ionSlideDidChange)="slideChanged()" [options]="slideOpts" [dir]="direction" role="tablist"
|
||||
<ion-slides (ionSlideDidChange)="slideChanged()" [options]="slidesOpts" [dir]="direction" role="tablist"
|
||||
[attr.aria-label]="description" aria-hidden="false">
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<ion-slide [hidden]="!hideUntil" [attr.aria-selected]="selected == tab.id" class="tab-slide"
|
||||
|
|
|
@ -82,7 +82,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
direction = 'ltr';
|
||||
description = '';
|
||||
lastScroll = 0;
|
||||
slideOpts = {
|
||||
slidesOpts = {
|
||||
initialSlide: 0,
|
||||
slidesPerView: 3,
|
||||
centerInsufficientSlides: true,
|
||||
|
@ -381,13 +381,12 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
protected async updateSlides(): Promise<void> {
|
||||
this.numTabsShown = this.tabs.reduce((prev: number, current: CoreTab) => current.enabled ? prev + 1 : prev, 0);
|
||||
|
||||
this.slideOpts.slidesPerView = Math.min(this.maxSlides, this.numTabsShown);
|
||||
this.slidesSwiper.params.slidesPerView = this.slideOpts.slidesPerView;
|
||||
this.slidesOpts = { ...this.slidesOpts, slidesPerView: Math.min(this.maxSlides, this.numTabsShown) };
|
||||
|
||||
this.calculateTabBarHeight();
|
||||
await this.slides!.update();
|
||||
|
||||
if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.slideOpts.slidesPerView) {
|
||||
if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.slidesOpts.slidesPerView) {
|
||||
this.hasSliddenToInitial = true;
|
||||
this.shouldSlideToInitial = true;
|
||||
|
||||
|
@ -637,6 +636,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
|
|||
window.removeEventListener('resize', this.resizeFunction);
|
||||
}
|
||||
this.stackEventsSubscription?.unsubscribe();
|
||||
this.languageChangedSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<img *ngIf="avatarUrl" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||
onError="this.src='assets/img/user-avatar.png'" role="presentation" (click)="gotoProfile($event)">
|
||||
|
||||
<img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}" role="presentation"
|
||||
(click)="gotoProfile($event)">
|
||||
|
||||
<span *ngIf="checkOnline && isOnline()" class="contact-status online"></span>
|
||||
|
||||
<img *ngIf="extraIcon" [src]="extraIcon" alt="" role="presentation" class="core-avatar-extra-icon">
|
||||
|
||||
<ng-content></ng-content>
|
|
@ -0,0 +1,43 @@
|
|||
:host {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
img {
|
||||
border-radius: 50%;
|
||||
width: var(--core-avatar-size);
|
||||
height: var(--core-avatar-size);
|
||||
}
|
||||
|
||||
.contact-status {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
&.online {
|
||||
border: 1px solid white;
|
||||
background-color: var(--core-online-color);
|
||||
}
|
||||
}
|
||||
|
||||
.core-avatar-extra-icon {
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: none;
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.toolbar) .contact-status {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
:host-context([dir="rtl"]) .contact-status {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
// (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 { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NavController } from '@ionic/angular';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreObject } from '@singletons/object';
|
||||
import { CoreUserProvider, CoreUserBasicData, CoreUserProfilePictureUpdatedData } from '@features/user/services/user';
|
||||
|
||||
/**
|
||||
* Component to display a "user avatar".
|
||||
*
|
||||
* Example: <core-user-avatar [user]="participant"></core-user-avatar>
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-user-avatar',
|
||||
templateUrl: 'core-user-avatar.html',
|
||||
styleUrls: ['user-avatar.scss'],
|
||||
})
|
||||
export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
@Input() user?: CoreUserWithAvatar;
|
||||
// The following params will override the ones in user object.
|
||||
@Input() profileUrl?: string;
|
||||
@Input() protected linkProfile = true; // Avoid linking to the profile if wanted.
|
||||
@Input() fullname?: string;
|
||||
@Input() protected userId?: number; // If provided or found it will be used to link the image to the profile.
|
||||
@Input() protected courseId?: number;
|
||||
@Input() checkOnline = false; // If want to check and show online status.
|
||||
@Input() extraIcon?: string; // Extra icon to show near the avatar.
|
||||
|
||||
avatarUrl?: string;
|
||||
|
||||
// Variable to check if we consider this user online or not.
|
||||
// @TODO: Use setting when available (see MDL-63972) so we can use site setting.
|
||||
protected timetoshowusers = 300000; // Miliseconds default.
|
||||
protected currentUserId: number;
|
||||
protected pictureObserver: CoreEventObserver;
|
||||
|
||||
constructor(
|
||||
protected navCtrl: NavController,
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
this.currentUserId = CoreSites.instance.getCurrentSiteUserId();
|
||||
|
||||
this.pictureObserver = CoreEvents.on<CoreUserProfilePictureUpdatedData>(
|
||||
CoreUserProvider.PROFILE_PICTURE_UPDATED,
|
||||
(data) => {
|
||||
if (data.userId == this.userId) {
|
||||
this.avatarUrl = data.picture;
|
||||
}
|
||||
},
|
||||
CoreSites.instance.getCurrentSiteId(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.setFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to changes.
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
// If something change, update the fields.
|
||||
if (changes) {
|
||||
this.setFields();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fields from user.
|
||||
*/
|
||||
protected setFields(): void {
|
||||
const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl ||
|
||||
this.user.userpictureurl || this.user.profileimageurlsmall || (this.user.urls && this.user.urls.profileimage)));
|
||||
|
||||
if (typeof profileUrl == 'string') {
|
||||
this.avatarUrl = profileUrl;
|
||||
}
|
||||
|
||||
this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
|
||||
|
||||
this.userId = this.userId || (this.user && (this.user.userid || this.user.id));
|
||||
this.courseId = this.courseId || (this.user && this.user.courseid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for checking the time meets the 'online' condition.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
isOnline(): boolean {
|
||||
if (!this.user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CoreUtils.instance.isFalseOrZero(this.user.isonline)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.user.lastaccess) {
|
||||
// If the time has passed, don't show the online status.
|
||||
const time = new Date().getTime() - this.timetoshowusers;
|
||||
|
||||
return this.user.lastaccess * 1000 >= time;
|
||||
} else {
|
||||
// You have to have Internet access first.
|
||||
return !!this.user.isonline && CoreApp.instance.isOnline();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to user profile.
|
||||
*
|
||||
* @param event Click event.
|
||||
*/
|
||||
gotoProfile(event: Event): void {
|
||||
if (!this.linkProfile || !this.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// @todo Decide which navCtrl to use. If this component is inside a split view, use the split view's master nav.
|
||||
this.navCtrl.navigateForward(['user'], {
|
||||
relativeTo: this.route,
|
||||
queryParams: CoreObject.removeUndefined({
|
||||
userId: this.userId,
|
||||
courseId: this.courseId,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.pictureObserver.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Type with all possible formats of user.
|
||||
*/
|
||||
type CoreUserWithAvatar = CoreUserBasicData & {
|
||||
userpictureurl?: string;
|
||||
userprofileimageurl?: string;
|
||||
profileimageurlsmall?: string;
|
||||
urls?: {
|
||||
profileimage?: string;
|
||||
};
|
||||
userfullname?: string;
|
||||
userid?: number;
|
||||
isonline?: boolean;
|
||||
courseid?: number;
|
||||
lastaccess?: number;
|
||||
};
|
|
@ -22,6 +22,7 @@ import { CoreLinkDirective } from './link';
|
|||
import { CoreLongPressDirective } from './long-press';
|
||||
import { CoreSupressEventsDirective } from './supress-events';
|
||||
import { CoreFaIconDirective } from './fa-icon';
|
||||
import { CoreUserLinkDirective } from './user-link';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -33,6 +34,7 @@ import { CoreFaIconDirective } from './fa-icon';
|
|||
CoreSupressEventsDirective,
|
||||
CoreFabDirective,
|
||||
CoreFaIconDirective,
|
||||
CoreUserLinkDirective,
|
||||
],
|
||||
imports: [],
|
||||
exports: [
|
||||
|
@ -44,6 +46,7 @@ import { CoreFaIconDirective } from './fa-icon';
|
|||
CoreSupressEventsDirective,
|
||||
CoreFabDirective,
|
||||
CoreFaIconDirective,
|
||||
CoreUserLinkDirective,
|
||||
],
|
||||
})
|
||||
export class CoreDirectivesModule {}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Directive, Input, OnInit, ElementRef } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NavController } from '@ionic/angular';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
import { CoreObject } from '@singletons/object';
|
||||
|
||||
/**
|
||||
* Directive to go to user profile on click.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[core-user-link]',
|
||||
})
|
||||
export class CoreUserLinkDirective implements OnInit {
|
||||
|
||||
@Input() userId?: number; // User id to open the profile.
|
||||
@Input() courseId?: number; // If set, course id to show the user info related to that course.
|
||||
|
||||
protected element: HTMLElement;
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
protected navCtrl: NavController,
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
this.element = element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed when the component is initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.element.addEventListener('click', (event) => {
|
||||
// If the event prevented default action, do nothing.
|
||||
if (event.defaultPrevented || !this.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// @todo If this directive is inside a split view, use the split view's master nav.
|
||||
CoreNavHelper.instance.goInCurrentMainMenuTab('user', CoreObject.removeUndefined({
|
||||
userId: this.userId,
|
||||
courseId: this.courseId,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -12,11 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreContentLinksHelper } from '../services/contentlinks-helper';
|
||||
import { CoreContentLinksHandlerBase } from './base-handler';
|
||||
import { Translate } from '@singletons';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreContentLinksAction } from '../services/contentlinks-delegate';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Handler to handle URLs pointing to a list of a certain type of modules.
|
||||
|
@ -65,7 +65,7 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa
|
|||
title: this.title || Translate.instance.instant('addon.mod_' + this.modName + '.modulenameplural'),
|
||||
};
|
||||
|
||||
CoreContentLinksHelper.instance.goInSite('CoreCourseListModTypePage @todo', stateParams, siteId);
|
||||
CoreNavHelper.instance.goInSite('CoreCourseListModTypePage @todo', stateParams, siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ import { NavController } from '@ionic/angular';
|
|||
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreContentLinksAction } from '../../services/contentlinks-delegate';
|
||||
import { CoreContentLinksHelper } from '../../services/contentlinks-helper';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page to display the list of sites to choose one to perform a content link action.
|
||||
|
@ -102,7 +102,7 @@ export class CoreContentLinksChooseSitePage implements OnInit {
|
|||
*/
|
||||
siteClicked(siteId: string): void {
|
||||
if (this.isRootURL) {
|
||||
CoreLoginHelper.instance.redirect('', {}, siteId);
|
||||
CoreNavHelper.instance.openInSiteMainMenu('', {}, siteId);
|
||||
} else if (this.action) {
|
||||
this.action.action(siteId);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,11 @@ import { Injectable } from '@angular/core';
|
|||
import { NavController } from '@ionic/angular';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreMainMenu } from '@features/mainmenu/services/mainmenu';
|
||||
import { makeSingleton, NgZone, Translate } from '@singletons';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding content links.
|
||||
|
@ -94,50 +92,10 @@ export class CoreContentLinksHelperProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked.
|
||||
* @return Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavHelperService.goInSite instead.
|
||||
*/
|
||||
goInSite(
|
||||
pageName: string,
|
||||
pageParams: Params,
|
||||
siteId?: string,
|
||||
checkMenu?: boolean,
|
||||
): Promise<void> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const deferred = CoreUtils.instance.promiseDefer<void>();
|
||||
|
||||
// Execute the code in the Angular zone, so change detection doesn't stop working.
|
||||
NgZone.instance.run(async () => {
|
||||
try {
|
||||
if (siteId == CoreSites.instance.getCurrentSiteId()) {
|
||||
if (checkMenu) {
|
||||
let isInMenu = false;
|
||||
// Check if the page is in the main menu.
|
||||
try {
|
||||
isInMenu = await CoreMainMenu.instance.isCurrentMainMenuHandler(pageName);
|
||||
} catch {
|
||||
isInMenu = false;
|
||||
}
|
||||
|
||||
if (isInMenu) {
|
||||
// Just select the tab. @todo test.
|
||||
CoreLoginHelper.instance.loadPageInMainMenu(pageName, pageParams);
|
||||
} else {
|
||||
await this.navCtrl.navigateForward(pageName, { queryParams: pageParams });
|
||||
}
|
||||
} else {
|
||||
await this.navCtrl.navigateForward(pageName, { queryParams: pageParams });
|
||||
}
|
||||
} else {
|
||||
await CoreLoginHelper.instance.redirect(pageName, pageParams, siteId);
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
} catch (error) {
|
||||
deferred.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
goInSite(pageName: string, pageParams: Params, siteId?: string, checkMenu?: boolean): Promise<void> {
|
||||
return CoreNavHelper.instance.goInSite(pageName, pageParams, siteId, checkMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,7 +192,7 @@ export class CoreContentLinksHelperProvider {
|
|||
}
|
||||
} else {
|
||||
// Login in the site.
|
||||
return CoreLoginHelper.instance.redirect('', {}, site.getId());
|
||||
return CoreNavHelper.instance.openInSiteMainMenu('', {}, site.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ import {
|
|||
} from '@features/courses/services/courses';
|
||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreLoginHelper, CoreLoginHelperProvider } from '@features/login/services/login-helper';
|
||||
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||
import { CoreCourseOffline } from './course-offline';
|
||||
import { CoreNavHelper, CoreNavHelperService } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Prefetch info of a module.
|
||||
|
@ -938,7 +938,7 @@ export class CoreCourseHelperProvider {
|
|||
params = params || {};
|
||||
Object.assign(params, { course: course });
|
||||
|
||||
return CoreLoginHelper.instance.redirect(CoreLoginHelperProvider.OPEN_COURSE, params, siteId);
|
||||
return CoreNavHelper.instance.openInSiteMainMenu(CoreNavHelperService.OPEN_COURSE, params, siteId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,19 +43,15 @@
|
|||
<h2>{{ 'core.teachers' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<!-- @todo <ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link
|
||||
[userId]="contact.id"
|
||||
[courseId]="isEnrolled ? course.id : null"
|
||||
[attr.aria-label]="'core.viewprofile' | translate">
|
||||
<ion-avatar core-user-avatar
|
||||
[user]="contact" slot="start"
|
||||
[userId]="contact.id"
|
||||
<ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link [userId]="contact.id"
|
||||
[courseId]="isEnrolled ? course.id : null" [attr.aria-label]="'core.viewprofile' | translate">
|
||||
<core-user-avatar [user]="contact" slot="start" [userId]="contact.id"
|
||||
[courseId]="isEnrolled ? course.id : null">
|
||||
</ion-avatar>
|
||||
</core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{contact.fullname}}</h2>
|
||||
</ion-label>
|
||||
</ion-item>-->
|
||||
</ion-item>
|
||||
<ion-item-divider></ion-item-divider>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreEditorRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreEditorRichTextEditorComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: [
|
||||
CoreEditorRichTextEditorComponent,
|
||||
],
|
||||
entryComponents: [
|
||||
CoreEditorRichTextEditorComponent,
|
||||
],
|
||||
})
|
||||
export class CoreEditorComponentsModule {}
|
|
@ -0,0 +1,113 @@
|
|||
<div class="core-rte-editor-container" (click)="focusRTE()" [class.toolbar-hidden]="toolbarHidden">
|
||||
<div [hidden]="!rteEnabled" #editor contenteditable="true" class="core-rte-editor" button (focus)="showToolbar($event)"
|
||||
(longPress)="showToolbar($event)" (blur)="hideToolbar($event)" [attr.data-placeholder-text]="placeholder" role="textbox">
|
||||
</div>
|
||||
|
||||
<ion-textarea [hidden]="rteEnabled" #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name"
|
||||
ngControl="control" (ionChange)="onChange()" (focus)="showToolbar($event)" (longPress)="showToolbar($event)"
|
||||
(blur)="hideToolbar($event)" role="textbox">
|
||||
</ion-textarea>
|
||||
|
||||
<div class="core-rte-info-message" *ngIf="infoMessage">
|
||||
<ion-icon name="fas-info-circle"></ion-icon>
|
||||
{{ infoMessage | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div #toolbar class="core-rte-toolbar" [class.toolbar-hidden]="toolbarHidden">
|
||||
<button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarPrevHidden"
|
||||
(click)="toolbarPrev($event)" (mousedown)="mouseDownAction($event)">
|
||||
<ion-icon name="fas-chevron-left"></ion-icon>
|
||||
</button>
|
||||
<ion-slides [options]="slidesOpts" [dir]="direction" (ionSlideDidChange)="updateToolbarArrows()">
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strong" [title]="'core.editor.bold' | translate"
|
||||
(click)="buttonAction($event, 'bold', 'strong')" (mousedown)="mouseDownAction($event)">
|
||||
<core-icon name="fas-bold"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.em" (click)="buttonAction($event, 'italic', 'em')"
|
||||
(mousedown)="mouseDownAction($event)" [title]=" 'core.editor.italic' | translate">
|
||||
<core-icon name="fas-italic"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.u" (click)="buttonAction($event, 'underline', 'u')"
|
||||
(mousedown)="mouseDownAction($event)" [title]="'core.editor.underline' | translate">
|
||||
<core-icon name="fas-underline"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strike" [title]="'core.editor.strike' | translate"
|
||||
(click)="buttonAction($event, 'strikethrough', 'strike')" (mousedown)="mouseDownAction($event)">
|
||||
<core-icon name="fas-strikethrough"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.p" (click)="buttonAction($event, 'p', 'block')"
|
||||
(mousedown)="mouseDownAction($event)" [title]="'core.editor.p' | translate">
|
||||
<core-icon name="fas-paragraph"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h3" (click)="buttonAction($event, 'h3', 'block')"
|
||||
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h3' | translate">
|
||||
<core-icon name="fas-heading"></core-icon>3
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h4" (click)="buttonAction($event, 'h4', 'block')"
|
||||
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h4' | translate">
|
||||
<core-icon name="fas-heading"></core-icon>4
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h5" (click)="buttonAction($event, 'h5', 'block')"
|
||||
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h5' | translate">
|
||||
<core-icon name="fas-heading"></core-icon>5
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ul" (mousedown)="mouseDownAction($event)"
|
||||
(click)="buttonAction($event, 'insertUnorderedList')" [title]="'core.editor.unorderedlist' | translate">
|
||||
<core-icon name="fas-list-ul"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ol" (mousedown)="mouseDownAction($event)"
|
||||
(click)="buttonAction($event, 'insertOrderedList')" [title]="'core.editor.orderedlist' | translate">
|
||||
<core-icon name="fas-list-ol"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [disabled]="!rteEnabled" (click)="buttonAction($event, 'removeFormat')" (mousedown)="mouseDownAction($event)"
|
||||
[title]="'core.editor.clear' | translate">
|
||||
<core-icon name="fas-eraser"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide *ngIf="canScanQR">
|
||||
<button [disabled]="!rteEnabled" (click)="scanQR($event)" (mousedown)="stopBubble($event)"
|
||||
[title]="'core.scanqr' | translate">
|
||||
<core-icon name="fas-qrcode"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide>
|
||||
<button [attr.aria-pressed]="!rteEnabled" (click)="toggleEditor($event)" (mousedown)="mouseDownAction($event)"
|
||||
[title]=" 'core.editor.toggle' | translate">
|
||||
<core-icon name="fas-code"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
<ion-slide *ngIf="isPhone">
|
||||
<button (click)="hideToolbar($event)" (mousedown)="mouseDownAction($event)"
|
||||
[title]=" 'core.editor.hidetoolbar' | translate">
|
||||
<core-icon name="fas-times"></core-icon>
|
||||
</button>
|
||||
</ion-slide>
|
||||
</ion-slides>
|
||||
<button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarNextHidden"
|
||||
(click)="toolbarNext($event)" (mousedown)="mouseDownAction($event)">
|
||||
<ion-icon name="fas-chevron-right"></ion-icon>
|
||||
</button>
|
||||
</div>
|
|
@ -0,0 +1,181 @@
|
|||
:host {
|
||||
height: 40vh;
|
||||
overflow: hidden;
|
||||
min-height: 200px; /* Just in case vh is not supported */
|
||||
min-height: 40vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// @include darkmode() {
|
||||
// background-color: $gray-darker;
|
||||
// }
|
||||
|
||||
.core-rte-editor-container {
|
||||
max-height: calc(100% - 46px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
&.toolbar-hidden {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.core-rte-info-message {
|
||||
padding: 5px;
|
||||
border-top: 1px solid var(--ion-color-secondary);
|
||||
background: white;
|
||||
flex-shrink: 1;
|
||||
font-size: 1.4rem;
|
||||
|
||||
.icon {
|
||||
color: var(--ion-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-rte-editor, .core-textarea {
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
background-color: white;
|
||||
flex-grow: 1;
|
||||
// @include darkmode() {
|
||||
// background-color: var(--gray-darker);
|
||||
// color: var(--white);
|
||||
// }
|
||||
}
|
||||
|
||||
.core-rte-editor {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
-webkit-user-select: auto !important;
|
||||
word-wrap: break-word;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
cursor: text;
|
||||
img {
|
||||
// @include padding(null, null, null, 2px);
|
||||
max-width: 95%;
|
||||
width: auto;
|
||||
}
|
||||
&:empty:before {
|
||||
content: attr(data-placeholder-text);
|
||||
display: block;
|
||||
color: var(--gray-light);
|
||||
font-weight: bold;
|
||||
|
||||
// @include darkmode() {
|
||||
// color: $gray;
|
||||
// }
|
||||
}
|
||||
|
||||
// Make empty elements selectable (to move the cursor).
|
||||
*:empty:after {
|
||||
content: '\200B';
|
||||
}
|
||||
}
|
||||
|
||||
.core-textarea {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
resize: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.core-rte-toolbar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--white);
|
||||
|
||||
// @include darkmode() {
|
||||
// background-color: $black;
|
||||
// }
|
||||
// @include padding(5px, null);
|
||||
border-top: 1px solid var(--gray);
|
||||
|
||||
ion-slides {
|
||||
width: 240px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
margin: 0 auto;
|
||||
font-size: 18px;
|
||||
background-color: var(--white);
|
||||
border-radius: 4px;
|
||||
// @include core-transition(background-color, 200ms);
|
||||
color: var(--ion-text-color);
|
||||
cursor: pointer;
|
||||
|
||||
// @include darkmode() {
|
||||
// background-color: $black;
|
||||
// color: $core-dark-text-color;
|
||||
// }
|
||||
|
||||
&.toolbar-button-enable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:active, &[aria-pressed="true"] {
|
||||
background-color: var(--gray);
|
||||
// @include darkmode() {
|
||||
// background-color: $gray-dark;
|
||||
// }
|
||||
}
|
||||
|
||||
&.toolbar-arrow {
|
||||
width: 28px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
opacity: 1;
|
||||
// @include core-transition(opacity, 200ms);
|
||||
|
||||
&:active {
|
||||
background-color: var(--white);
|
||||
// @include darkmode() {
|
||||
// background-color: $black;
|
||||
// }
|
||||
}
|
||||
|
||||
&.toolbar-arrow-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toolbar-hidden {
|
||||
visibility: none;
|
||||
height: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.keyboard-is-open) {
|
||||
min-height: 200px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
|
||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||
import { CoreEditorComponentsModule } from './components/components.module';
|
||||
import { SITE_SCHEMA } from './services/database/editor';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: CORE_SITE_SCHEMAS,
|
||||
useValue: [SITE_SCHEMA],
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CoreEditorModule {}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"autosavesucceeded": "Draft saved.",
|
||||
"bold": "Bold",
|
||||
"clear": "Clear formatting",
|
||||
"h3": "Heading (large)",
|
||||
"h4": "Heading (medium)",
|
||||
"h5": "Heading (small)",
|
||||
"hidetoolbar": "Hide toolbar",
|
||||
"italic": "Italic",
|
||||
"orderedlist": "Ordered list",
|
||||
"p": "Paragraph",
|
||||
"strike": "Strike through",
|
||||
"textrecovered": "A draft version of this text was automatically restored.",
|
||||
"toggle": "Toggle editor",
|
||||
"underline": "Underline",
|
||||
"unorderedlist": "Unordered list"
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// (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 { CoreSiteSchema } from '@services/sites';
|
||||
|
||||
/**
|
||||
* Database variables for CoreEditorOffline service.
|
||||
*/
|
||||
export const DRAFT_TABLE = 'editor_draft';
|
||||
export const SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'CoreEditorProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: DRAFT_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'contextlevel',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'contextinstanceid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'elementid',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'extraparams', // Moodle web uses a page hash built with URL. App will use some params stringified.
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'drafttext',
|
||||
type: 'TEXT',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'pageinstance',
|
||||
type: 'TEXT',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'originalcontent',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Primary data to identify a stored draft.
|
||||
*/
|
||||
export type CoreEditorDraftPrimaryData = {
|
||||
contextlevel: string; // Context level.
|
||||
contextinstanceid: number; // The instance ID related to the context.
|
||||
elementid: string; // Element ID.
|
||||
extraparams: string; // Extra params stringified.
|
||||
};
|
||||
|
||||
/**
|
||||
* Draft data stored.
|
||||
*/
|
||||
export type CoreEditorDraft = CoreEditorDraftPrimaryData & {
|
||||
drafttext?: string; // Draft text stored.
|
||||
pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
|
||||
timecreated?: number; // Time created.
|
||||
timemodified?: number; // Time modified.
|
||||
originalcontent?: string; // Original content of the editor.
|
||||
};
|
|
@ -0,0 +1,239 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreEditorDraft, CoreEditorDraftPrimaryData, DRAFT_TABLE } from './database/editor';
|
||||
|
||||
/**
|
||||
* Service with features regarding rich text editor in offline.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreEditorOfflineProvider {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreEditorOfflineProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a draft from DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async deleteDraft(
|
||||
contextLevel: string,
|
||||
contextInstanceId: number,
|
||||
elementId: string,
|
||||
extraParams: Record<string, unknown>,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
await db.deleteRecords(DRAFT_TABLE, params);
|
||||
} catch (error) {
|
||||
// Ignore errors, probably no draft stored.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object with the draft primary data converted to the right format.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @return Object with the fixed primary data.
|
||||
*/
|
||||
protected fixDraftPrimaryData(
|
||||
contextLevel: string,
|
||||
contextInstanceId: number,
|
||||
elementId: string,
|
||||
extraParams: Record<string, unknown>,
|
||||
): CoreEditorDraftPrimaryData {
|
||||
|
||||
return {
|
||||
contextlevel: contextLevel,
|
||||
contextinstanceid: contextInstanceId,
|
||||
elementid: elementId,
|
||||
extraparams: CoreUtils.instance.sortAndStringify(extraParams || {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a draft from DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the draft data. Undefined if no draft stored.
|
||||
*/
|
||||
async getDraft(
|
||||
contextLevel: string,
|
||||
contextInstanceId: number,
|
||||
elementId: string,
|
||||
extraParams: Record<string, unknown>,
|
||||
siteId?: string,
|
||||
): Promise<CoreEditorDraft> {
|
||||
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
return db.getRecord(DRAFT_TABLE, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get draft to resume it.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||
* @param originalContent Original content of the editor.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the draft data. Undefined if no draft stored.
|
||||
*/
|
||||
async resumeDraft(
|
||||
contextLevel: string,
|
||||
contextInstanceId: number,
|
||||
elementId: string,
|
||||
extraParams: Record<string, unknown>,
|
||||
pageInstance: string,
|
||||
originalContent?: string,
|
||||
siteId?: string,
|
||||
): Promise<CoreEditorDraft | undefined> {
|
||||
|
||||
try {
|
||||
// Check if there is a draft stored.
|
||||
const entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||
|
||||
// There is a draft stored. Update its page instance.
|
||||
try {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
entry.pageinstance = pageInstance;
|
||||
entry.timemodified = Date.now();
|
||||
|
||||
if (originalContent && entry.originalcontent != originalContent) {
|
||||
entry.originalcontent = originalContent;
|
||||
entry.drafttext = ''; // "Discard" the draft.
|
||||
}
|
||||
|
||||
await db.insertRecord(DRAFT_TABLE, entry);
|
||||
} catch (error) {
|
||||
// Ignore errors saving the draft. It shouldn't happen.
|
||||
}
|
||||
|
||||
return entry;
|
||||
} catch (error) {
|
||||
// No draft stored. Store an empty draft to save the pageinstance.
|
||||
await this.saveDraft(
|
||||
contextLevel,
|
||||
contextInstanceId,
|
||||
elementId,
|
||||
extraParams,
|
||||
pageInstance,
|
||||
'',
|
||||
originalContent,
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a draft in DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||
* @param draftText The text to store.
|
||||
* @param originalContent Original content of the editor.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async saveDraft(
|
||||
contextLevel: string,
|
||||
contextInstanceId: number,
|
||||
elementId: string,
|
||||
extraParams: Record<string, unknown>,
|
||||
pageInstance: string,
|
||||
draftText: string,
|
||||
originalContent?: string,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
|
||||
let timecreated = Date.now();
|
||||
let entry: CoreEditorDraft | undefined;
|
||||
|
||||
// Check if there is a draft already stored.
|
||||
try {
|
||||
entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||
|
||||
timecreated = entry.timecreated || timecreated;
|
||||
} catch (error) {
|
||||
// No draft already stored.
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
if (entry.pageinstance != pageInstance) {
|
||||
this.logger.warn(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
|
||||
`element '${elementId}'`);
|
||||
|
||||
throw new CoreError('Draft was discarded because it was modified in another page.');
|
||||
}
|
||||
|
||||
if (!originalContent) {
|
||||
// Original content not set, use the one in the entry.
|
||||
originalContent = entry.originalcontent;
|
||||
}
|
||||
}
|
||||
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
data.drafttext = (draftText || '').trim();
|
||||
data.pageinstance = pageInstance;
|
||||
data.timecreated = timecreated;
|
||||
data.timemodified = Date.now();
|
||||
if (originalContent) {
|
||||
data.originalcontent = originalContent;
|
||||
}
|
||||
|
||||
await db.insertRecord(DRAFT_TABLE, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreEditorOffline extends makeSingleton(CoreEditorOfflineProvider) {}
|
|
@ -23,6 +23,7 @@ import { CoreMainMenuModule } from './mainmenu/mainmenu.module';
|
|||
import { CoreSettingsModule } from './settings/settings.module';
|
||||
import { CoreSiteHomeModule } from './sitehome/sitehome.module';
|
||||
import { CoreTagModule } from './tag/tag.module';
|
||||
import { CoreUserModule } from './user/user.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -35,6 +36,7 @@ import { CoreTagModule } from './tag/tag.module';
|
|||
CoreSettingsModule,
|
||||
CoreSiteHomeModule,
|
||||
CoreTagModule,
|
||||
CoreUserModule,
|
||||
],
|
||||
})
|
||||
export class CoreFeaturesModule {}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { CoreSites } from '@services/sites';
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page that shows instructions to change the password.
|
||||
|
@ -62,7 +63,7 @@ export class CoreLoginChangePasswordPage {
|
|||
* Login the user.
|
||||
*/
|
||||
login(): void {
|
||||
CoreLoginHelper.instance.goToSiteInitialPage();
|
||||
CoreNavHelper.instance.goToSiteInitialPage();
|
||||
this.changingPassword = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import { CoreConstants } from '@/core/constants';
|
|||
import { Translate } from '@singletons';
|
||||
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page to enter the user credentials.
|
||||
|
@ -244,7 +245,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
|
||||
this.siteId = id;
|
||||
|
||||
await CoreLoginHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen });
|
||||
await CoreNavHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen });
|
||||
} catch (error) {
|
||||
CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password);
|
||||
|
||||
|
|
|
@ -172,8 +172,8 @@
|
|||
<ion-item-divider class="ion-text-wrap">
|
||||
<ion-label>{{ category.name }}</ion-label>
|
||||
</ion-item-divider>
|
||||
<!-- @todo <core-user-profile-field *ngFor="let field of category.fields" [field]="field" edit="true" signup="true"
|
||||
registerAuth="email" [form]="signupForm"></core-user-profile-field> -->
|
||||
<core-user-profile-field *ngFor="let field of category.fields" [field]="field" [edit]="true" [signup]="true"
|
||||
registerAuth="email" [form]="signupForm"></core-user-profile-field>
|
||||
</ng-container>
|
||||
|
||||
<!-- ReCAPTCHA -->
|
||||
|
|
|
@ -21,6 +21,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreUserComponentsModule } from '@features/user/components/components.module';
|
||||
|
||||
import { CoreLoginEmailSignupPage } from './email-signup';
|
||||
|
||||
|
@ -41,6 +42,7 @@ const routes: Routes = [
|
|||
ReactiveFormsModule,
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreUserComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreLoginEmailSignupPage,
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CoreWS, CoreWSExternalWarning } from '@services/ws';
|
|||
import { CoreConstants } from '@/core/constants';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
|
||||
import {
|
||||
AuthEmailSignupProfileFieldsCategory,
|
||||
|
@ -156,7 +157,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
if (typeof this.ageDigitalConsentVerification == 'undefined') {
|
||||
|
||||
const result = await CoreUtils.instance.ignoreErrors(
|
||||
CoreWS.instance.callAjax<IsAgeVerificationEnabledResponse>(
|
||||
CoreWS.instance.callAjax<IsAgeVerificationEnabledWSResponse>(
|
||||
'core_auth_is_age_digital_consent_verification_enabled',
|
||||
{},
|
||||
{ siteUrl: this.siteUrl },
|
||||
|
@ -189,7 +190,11 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
{ siteUrl: this.siteUrl },
|
||||
);
|
||||
|
||||
// @todo userProfileFieldDelegate
|
||||
if (CoreUserProfileFieldDelegate.instance.hasRequiredUnsupportedField(this.settings.profilefields)) {
|
||||
this.allRequiredSupported = false;
|
||||
|
||||
throw new Error(Translate.instance.instant('core.login.signuprequiredfieldnotsupported'));
|
||||
}
|
||||
|
||||
this.categories = CoreLoginHelper.instance.formatProfileFieldsForSignup(this.settings.profilefields);
|
||||
|
||||
|
@ -274,7 +279,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
|
||||
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
|
||||
|
||||
const params: Record<string, unknown> = {
|
||||
const params: SignupUserWSParams = {
|
||||
username: this.signupForm.value.username.trim().toLowerCase(),
|
||||
password: this.signupForm.value.password,
|
||||
firstname: CoreTextUtils.instance.cleanTags(this.signupForm.value.firstname),
|
||||
|
@ -295,8 +300,15 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
}
|
||||
|
||||
try {
|
||||
// @todo Get the data for the custom profile fields.
|
||||
const result = await CoreWS.instance.callAjax<SignupUserResult>(
|
||||
// Get the data for the custom profile fields.
|
||||
params.customprofilefields = await CoreUserProfileFieldDelegate.instance.getDataForFields(
|
||||
this.settings?.profilefields,
|
||||
true,
|
||||
'email',
|
||||
this.signupForm.value,
|
||||
);
|
||||
|
||||
const result = await CoreWS.instance.callAjax<SignupUserWSResult>(
|
||||
'auth_email_signup_user',
|
||||
params,
|
||||
{ siteUrl: this.siteUrl },
|
||||
|
@ -376,7 +388,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
params.age = parseInt(params.age, 10); // Use just the integer part.
|
||||
|
||||
try {
|
||||
const result = await CoreWS.instance.callAjax<IsMinorResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl });
|
||||
const result = await CoreWS.instance.callAjax<IsMinorWSResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl });
|
||||
|
||||
CoreDomUtils.instance.triggerFormSubmittedEvent(this.ageFormElement, true);
|
||||
|
||||
|
@ -404,14 +416,35 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
/**
|
||||
* Result of WS core_auth_is_age_digital_consent_verification_enabled.
|
||||
*/
|
||||
export type IsAgeVerificationEnabledResponse = {
|
||||
type IsAgeVerificationEnabledWSResponse = {
|
||||
status: boolean; // True if digital consent verification is enabled, false otherwise.
|
||||
};
|
||||
|
||||
/**
|
||||
* Params for WS auth_email_signup_user.
|
||||
*/
|
||||
type SignupUserWSParams = {
|
||||
username: string; // Username.
|
||||
password: string; // Plain text password.
|
||||
firstname: string; // The first name(s) of the user.
|
||||
lastname: string; // The family name of the user.
|
||||
email: string; // A valid and unique email address.
|
||||
city?: string; // Home city of the user.
|
||||
country?: string; // Home country code.
|
||||
recaptchachallengehash?: string; // Recaptcha challenge hash.
|
||||
recaptcharesponse?: string; // Recaptcha response.
|
||||
customprofilefields?: { // User custom fields (also known as user profile fields).
|
||||
type: string; // The type of the custom field.
|
||||
name: string; // The name of the custom field.
|
||||
value: unknown; // Custom field value, can be an encoded json if required.
|
||||
}[];
|
||||
redirect?: string; // Redirect the user to this site url after confirmation.
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of WS auth_email_signup_user.
|
||||
*/
|
||||
export type SignupUserResult = {
|
||||
type SignupUserWSResult = {
|
||||
success: boolean; // True if the user was created false otherwise.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
@ -419,6 +452,6 @@ export type SignupUserResult = {
|
|||
/**
|
||||
* Result of WS core_auth_is_minor.
|
||||
*/
|
||||
export type IsMinorResult = {
|
||||
type IsMinorWSResult = {
|
||||
status: boolean; // True if the user is considered to be a digital minor, false if not.
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import { ApplicationInit, SplashScreen } from '@singletons';
|
|||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page that displays a "splash screen" while the app is being initialized.
|
||||
|
@ -79,7 +80,7 @@ export class CoreLoginInitPage implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
return CoreLoginHelper.instance.goToSiteInitialPage({
|
||||
return CoreNavHelper.instance.goToSiteInitialPage({
|
||||
redirectPage: redirectData.page,
|
||||
redirectParams: redirectData.params,
|
||||
});
|
||||
|
@ -89,7 +90,7 @@ export class CoreLoginInitPage implements OnInit {
|
|||
}
|
||||
} else if (redirectData.page) {
|
||||
// No site to load, open the page.
|
||||
return CoreLoginHelper.instance.goToNoSitePage(redirectData.page, redirectData.params);
|
||||
return CoreNavHelper.instance.goToNoSitePage(redirectData.page, redirectData.params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +110,7 @@ export class CoreLoginInitPage implements OnInit {
|
|||
return this.loadPage();
|
||||
}
|
||||
|
||||
return CoreLoginHelper.instance.goToSiteInitialPage();
|
||||
return CoreNavHelper.instance.goToSiteInitialPage();
|
||||
}
|
||||
|
||||
await this.navCtrl.navigateRoot('/login/sites');
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
|
|||
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page to enter the user password to reconnect to a site.
|
||||
|
@ -206,7 +207,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
this.credForm.controls['password'].reset();
|
||||
|
||||
// Go to the site initial page.
|
||||
await CoreLoginHelper.instance.goToSiteInitialPage({
|
||||
await CoreNavHelper.instance.goToSiteInitialPage({
|
||||
redirectPage: this.page,
|
||||
redirectParams: this.pageParams,
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page to accept a site policy.
|
||||
|
@ -128,7 +129,7 @@ export class CoreLoginSitePolicyPage implements OnInit {
|
|||
// Invalidate cache since some WS don't return error if site policy is not accepted.
|
||||
await CoreUtils.instance.ignoreErrors(this.currentSite!.invalidateWsCache());
|
||||
|
||||
await CoreLoginHelper.instance.goToSiteInitialPage();
|
||||
await CoreNavHelper.instance.goToSiteInitialPage();
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error accepting site policy.');
|
||||
} finally {
|
||||
|
|
|
@ -31,6 +31,7 @@ import { CoreUrl } from '@singletons/url';
|
|||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help';
|
||||
import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page that displays a "splash screen" while the app is being initialized.
|
||||
|
@ -328,7 +329,7 @@ export class CoreLoginSitePage implements OnInit {
|
|||
|
||||
CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true);
|
||||
|
||||
return CoreLoginHelper.instance.goToSiteInitialPage();
|
||||
return CoreNavHelper.instance.goToSiteInitialPage();
|
||||
} catch (error) {
|
||||
CoreLoginHelper.instance.treatUserTokenError(siteData.url, error, siteData.username, siteData.password);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page that displays a "splash screen" while the app is being initialized.
|
||||
|
@ -124,7 +125,7 @@ export class CoreLoginSitesPage implements OnInit {
|
|||
const loggedIn = await CoreSites.instance.loadSite(siteId);
|
||||
|
||||
if (loggedIn) {
|
||||
return CoreLoginHelper.instance.goToSiteInitialPage();
|
||||
return CoreNavHelper.instance.goToSiteInitialPage();
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error loading site ' + siteId, error);
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { Params } from '@angular/router';
|
||||
import { NavController } from '@ionic/angular';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
|
@ -34,8 +33,8 @@ import { CoreWSError } from '@classes/errors/wserror';
|
|||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
|
||||
import { CoreObject } from '@singletons/object';
|
||||
import { CoreNavHelper, CoreNavHelperOpenMainMenuOptions, CoreNavHelperService } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Helper provider that provides some common features regarding authentication.
|
||||
|
@ -43,7 +42,7 @@ import { CoreObject } from '@singletons/object';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreLoginHelperProvider {
|
||||
|
||||
static readonly OPEN_COURSE = 'open_course';
|
||||
static readonly OPEN_COURSE = CoreNavHelperService.OPEN_COURSE; // @deprecated since 3.9.5.
|
||||
static readonly ONBOARDING_DONE = 'onboarding_done';
|
||||
static readonly FAQ_URL_IMAGE_HTML = '<img src="assets/img/login/faq_url.png" role="presentation">';
|
||||
static readonly FAQ_QRCODE_IMAGE_HTML = '<img src="assets/img/login/faq_qrcode.png" role="presentation">';
|
||||
|
@ -51,24 +50,13 @@ export class CoreLoginHelperProvider {
|
|||
protected logger: CoreLogger;
|
||||
protected isSSOConfirmShown = false;
|
||||
protected isOpenEditAlertShown = false;
|
||||
protected pageToLoad?: {page: string; params?: Params; time: number}; // Page to load once main menu is opened.
|
||||
protected isOpeningReconnect = false;
|
||||
waitingForBrowser = false;
|
||||
|
||||
constructor(
|
||||
protected location: Location,
|
||||
protected navCtrl: NavController,
|
||||
) {
|
||||
this.logger = CoreLogger.getInstance('CoreLoginHelper');
|
||||
|
||||
CoreEvents.on(CoreEvents.MAIN_MENU_OPEN, () => {
|
||||
/* If there is any page pending to be opened, do it now. Don't open pages stored more than 5 seconds ago, probably
|
||||
the function to open the page was called when it shouldn't. */
|
||||
if (this.pageToLoad && Date.now() - this.pageToLoad.time < 5000) {
|
||||
this.loadPageInMainMenu(this.pageToLoad.page, this.pageToLoad.params);
|
||||
delete this.pageToLoad;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,7 +111,7 @@ export class CoreLoginHelperProvider {
|
|||
*/
|
||||
checkLogout(): void {
|
||||
const currentSite = CoreSites.instance.getCurrentSite();
|
||||
const currentPage = CoreApp.instance.getCurrentPage();
|
||||
const currentPage = CoreNavHelper.instance.getCurrentPage();
|
||||
|
||||
if (!CoreApp.instance.isSSOAuthenticationOngoing() && currentSite?.isLoggedOut() && currentPage == '/login/reconnect') {
|
||||
// User must reauthenticate but he closed the InAppBrowser without doing so, logout him.
|
||||
|
@ -448,39 +436,10 @@ export class CoreLoginHelperProvider {
|
|||
* @param page Page to open.
|
||||
* @param params Params of the page.
|
||||
* @return Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavHelperService.goToNoSitePage instead.
|
||||
*/
|
||||
async goToNoSitePage(page: string, params?: Params): Promise<void> {
|
||||
const currentPage = CoreApp.instance.getCurrentPage();
|
||||
|
||||
if (currentPage == page) {
|
||||
// Already at page, nothing to do.
|
||||
} else if (page == '/login/sites') {
|
||||
// Just open the page as root.
|
||||
await this.navCtrl.navigateRoot(page, { queryParams: params });
|
||||
} else if (page == '/login/credentials' && currentPage == '/login/site') {
|
||||
// Just open the new page to keep the navigation history.
|
||||
await this.navCtrl.navigateForward(page, { queryParams: params });
|
||||
} else {
|
||||
// Check if there is any site stored.
|
||||
const hasSites = await CoreSites.instance.hasSites();
|
||||
|
||||
if (!hasSites) {
|
||||
// There are sites stored, open sites page first to be able to go back.
|
||||
await this.navCtrl.navigateRoot('/login/sites');
|
||||
|
||||
await this.navCtrl.navigateForward(page, { queryParams: params });
|
||||
} else {
|
||||
if (page != '/login/site') {
|
||||
// Open the new site page to be able to go back.
|
||||
await this.navCtrl.navigateRoot('/login/site');
|
||||
|
||||
await this.navCtrl.navigateForward(page, { queryParams: params });
|
||||
} else {
|
||||
// Just open the page as root.
|
||||
await this.navCtrl.navigateRoot(page, { queryParams: params });
|
||||
}
|
||||
}
|
||||
}
|
||||
goToNoSitePage(page: string, params?: Params): Promise<void> {
|
||||
return CoreNavHelper.instance.goToNoSitePage(page, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -488,9 +447,10 @@ export class CoreLoginHelperProvider {
|
|||
*
|
||||
* @param options Options.
|
||||
* @return Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavHelperService.goToSiteInitialPage instead.
|
||||
*/
|
||||
goToSiteInitialPage(options?: OpenMainMenuOptions): Promise<void> {
|
||||
return this.openMainMenu(options);
|
||||
goToSiteInitialPage(options?: CoreNavHelperOpenMainMenuOptions): Promise<void> {
|
||||
return CoreNavHelper.instance.goToSiteInitialPage(options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -641,97 +601,15 @@ export class CoreLoginHelperProvider {
|
|||
return code == CoreConstants.LOGIN_SSO_CODE || code == CoreConstants.LOGIN_SSO_INAPP_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a site and load a certain page in that site.
|
||||
*
|
||||
* @param siteId Site to load.
|
||||
* @param page Name of the page to load.
|
||||
* @param params Params to pass to the page.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadSiteAndPage(siteId: string, page: string, params?: Params): Promise<void> {
|
||||
if (siteId == CoreConstants.NO_SITE_ID) {
|
||||
// Page doesn't belong to a site, just load the page.
|
||||
await this.navCtrl.navigateRoot(page, params);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = await CoreDomUtils.instance.showModalLoading();
|
||||
|
||||
try {
|
||||
const loggedIn = await CoreSites.instance.loadSite(siteId, page, params);
|
||||
|
||||
if (!loggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.openMainMenu({
|
||||
redirectPage: page,
|
||||
redirectParams: params,
|
||||
});
|
||||
} catch (error) {
|
||||
// Site doesn't exist.
|
||||
await this.navCtrl.navigateRoot('/login/sites');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a certain page in the main menu page.
|
||||
*
|
||||
* @param page Name of the page to load.
|
||||
* @param params Params to pass to the page.
|
||||
* @deprecated since 3.9.5. Use CoreNavHelperService.loadPageInMainMenu instead.
|
||||
*/
|
||||
loadPageInMainMenu(page: string, params?: Params): void {
|
||||
if (!CoreApp.instance.isMainMenuOpen()) {
|
||||
// Main menu not open. Store the page to be loaded later.
|
||||
this.pageToLoad = {
|
||||
page: page,
|
||||
params: params,
|
||||
time: Date.now(),
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (page == CoreLoginHelperProvider.OPEN_COURSE) {
|
||||
// @todo Use the openCourse function.
|
||||
} else {
|
||||
CoreEvents.trigger(CoreEvents.LOAD_PAGE_MAIN_MENU, { redirectPage: page, redirectParams: params });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the main menu, loading a certain page.
|
||||
*
|
||||
* @param options Options.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async openMainMenu(options?: OpenMainMenuOptions): Promise<void> {
|
||||
|
||||
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
|
||||
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
|
||||
// @todo this.location.replaceState('');
|
||||
|
||||
if (options?.redirectPage == CoreLoginHelperProvider.OPEN_COURSE) {
|
||||
// Load the main menu first, and then open the course.
|
||||
try {
|
||||
await this.navCtrl.navigateRoot('/');
|
||||
} finally {
|
||||
// @todo: Open course.
|
||||
}
|
||||
} else {
|
||||
// Open the main menu.
|
||||
const queryParams: Params = Object.assign({}, options);
|
||||
delete queryParams.navigationOptions;
|
||||
|
||||
await this.navCtrl.navigateRoot('/', {
|
||||
queryParams,
|
||||
...options?.navigationOptions,
|
||||
});
|
||||
}
|
||||
CoreNavHelper.instance.loadPageInMainMenu(page, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -889,7 +767,7 @@ export class CoreLoginHelperProvider {
|
|||
return; // Site that triggered the event is not current site.
|
||||
}
|
||||
|
||||
const currentPage = CoreApp.instance.getCurrentPage();
|
||||
const currentPage = CoreNavHelper.instance.getCurrentPage();
|
||||
|
||||
// If current page is already change password, stop.
|
||||
if (currentPage == '/login/changepassword') {
|
||||
|
@ -948,31 +826,14 @@ export class CoreLoginHelperProvider {
|
|||
/**
|
||||
* Redirect to a new page, setting it as the root page and loading the right site if needed.
|
||||
*
|
||||
* @param page Name of the page to load. Special cases: OPEN_COURSE (to open course page).
|
||||
* @param page Name of the page to load. Special cases: CoreNavHelperService.OPEN_COURSE (to open course page).
|
||||
* @param params Params to pass to the page.
|
||||
* @param siteId Site to load. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
* @deprecated since 3.9.5. Use CoreNavHelperService.openInSiteMainMenu instead.
|
||||
*/
|
||||
async redirect(page: string, params?: Params, siteId?: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
if (CoreSites.instance.isLoggedIn()) {
|
||||
if (siteId && siteId != CoreSites.instance.getCurrentSiteId()) {
|
||||
// Target page belongs to a different site. Change site.
|
||||
// @todo: Check site plugins.
|
||||
await CoreSites.instance.logout();
|
||||
|
||||
await this.loadSiteAndPage(siteId, page, params);
|
||||
} else {
|
||||
this.loadPageInMainMenu(page, params);
|
||||
}
|
||||
} else {
|
||||
if (siteId) {
|
||||
await this.loadSiteAndPage(siteId, page, params);
|
||||
} else {
|
||||
await this.navCtrl.navigateRoot('/login/sites');
|
||||
}
|
||||
}
|
||||
redirect(page: string, params?: Params, siteId?: string): Promise<void> {
|
||||
return CoreNavHelper.instance.openInSiteMainMenu(page, params, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1098,7 +959,7 @@ export class CoreLoginHelperProvider {
|
|||
const info = currentSite.getInfo();
|
||||
if (typeof info != 'undefined' && typeof info.username != 'undefined' && !this.isOpeningReconnect) {
|
||||
// If current page is already reconnect, stop.
|
||||
if (CoreApp.instance.getCurrentPage() == '/login/reconnect') {
|
||||
if (CoreNavHelper.instance.getCurrentPage() == '/login/reconnect') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1266,7 +1127,7 @@ export class CoreLoginHelperProvider {
|
|||
}
|
||||
|
||||
// If current page is already site policy, stop.
|
||||
if (CoreApp.instance.getCurrentPage() == '/login/sitepolicy') {
|
||||
if (CoreNavHelper.instance.getCurrentPage() == '/login/sitepolicy') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1474,10 +1335,3 @@ type StoredLoginLaunchData = {
|
|||
pageParams: Params;
|
||||
ssoUrlParams: CoreUrlParams;
|
||||
};
|
||||
|
||||
type OpenMainMenuOptions = {
|
||||
redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected.
|
||||
redirectParams?: Params; // Params to pass to the selected tab if any.
|
||||
urlToOpen?: string; // URL to open once the main menu is loaded.
|
||||
navigationOptions?: NavigationOptions; // Navigation options.
|
||||
};
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// (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 { InjectionToken, Injector, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { Route, Routes } from '@angular/router';
|
||||
|
||||
import { ModuleRoutes, resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
|
||||
export const MAIN_MENU_TAB_ROUTES = new InjectionToken('MAIN_MENU_TAB_ROUTES');
|
||||
|
||||
export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes {
|
||||
const routes = resolveModuleRoutes(injector, MAIN_MENU_TAB_ROUTES);
|
||||
|
||||
mainRoute.path = mainRoute.path || '';
|
||||
mainRoute.children = mainRoute.children || [];
|
||||
mainRoute.children.concat(routes.children);
|
||||
|
||||
return [
|
||||
mainRoute,
|
||||
...routes.siblings,
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule()
|
||||
export class CoreMainMenuTabRoutingModule {
|
||||
|
||||
static forChild(routes: Partial<ModuleRoutes>): ModuleWithProviders<CoreMainMenuTabRoutingModule> {
|
||||
return {
|
||||
ngModule: CoreMainMenuTabRoutingModule,
|
||||
providers: [
|
||||
{ provide: MAIN_MENU_TAB_ROUTES, multi: true, useValue: routes },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -24,16 +24,17 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
|
||||
import { CoreMainMenuHomePage } from './home';
|
||||
import { MAIN_MENU_HOME_ROUTES } from './home-routing.module';
|
||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
const routes = resolveModuleRoutes(injector, MAIN_MENU_HOME_ROUTES);
|
||||
|
||||
return [
|
||||
{
|
||||
...buildTabMainRoutes(injector, {
|
||||
path: '',
|
||||
component: CoreMainMenuHomePage,
|
||||
children: routes.children,
|
||||
},
|
||||
}),
|
||||
...routes.siblings,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CoreMainMenu } from '../../services/mainmenu';
|
|||
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Page that displays the main menu of the app.
|
||||
|
@ -60,7 +61,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
protected changeDetector: ChangeDetectorRef,
|
||||
protected router: Router,
|
||||
) {
|
||||
this.mainMenuId = CoreApp.instance.getMainMenuId();
|
||||
this.mainMenuId = CoreNavHelper.instance.getMainMenuId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,7 +132,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
CoreApp.instance.setMainMenuOpen(this.mainMenuId, true);
|
||||
CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,7 +227,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
this.subscription?.unsubscribe();
|
||||
this.redirectObs?.off();
|
||||
window.removeEventListener('resize', this.initHandlers.bind(this));
|
||||
CoreApp.instance.setMainMenuOpen(this.mainMenuId, false);
|
||||
CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, false);
|
||||
this.keyboardObserver?.off();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item *ngIf="siteInfo" class="ion-text-wrap"> <!-- @todo core-user-link [userId]="siteInfo.userid" -->
|
||||
<ion-avatar slot="start"></ion-avatar> <!-- @todo core-user-avatar [user]="siteInfo" -->
|
||||
<ion-item button *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid">
|
||||
<core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{siteInfo.fullname}}</h2>
|
||||
<p>
|
||||
|
|
|
@ -14,29 +14,14 @@
|
|||
|
||||
import { Injector, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||
import { RouterModule, ROUTES } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
import { CoreMainMenuMorePage } from './more';
|
||||
import { MAIN_MENU_MORE_ROUTES } from './more-routing.module';
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
const routes = resolveModuleRoutes(injector, MAIN_MENU_MORE_ROUTES);
|
||||
|
||||
return [
|
||||
{
|
||||
path: '',
|
||||
component: CoreMainMenuMorePage,
|
||||
children: routes.children,
|
||||
},
|
||||
...routes.siblings,
|
||||
];
|
||||
}
|
||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -47,7 +32,14 @@ function buildRoutes(injector: Injector): Routes {
|
|||
CoreDirectivesModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },
|
||||
{
|
||||
provide: ROUTES,
|
||||
multi: true,
|
||||
deps: [Injector],
|
||||
useFactory: (injector: Injector) => buildTabMainRoutes(injector, {
|
||||
component: CoreMainMenuMorePage,
|
||||
}),
|
||||
},
|
||||
],
|
||||
declarations: [
|
||||
CoreMainMenuMorePage,
|
||||
|
|
|
@ -16,7 +16,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core';
|
|||
import { Routes } from '@angular/router';
|
||||
|
||||
import { AppRoutingModule } from '@/app/app-routing.module';
|
||||
import { CoreMainMenuMoreRoutingModule } from '@features/mainmenu/pages/more/more-routing.module';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
import { CoreSettingsHelperProvider } from './services/settings-helper';
|
||||
|
||||
|
@ -41,7 +41,7 @@ const mainMenuMoreRoutes: Routes = [
|
|||
@NgModule({
|
||||
imports: [
|
||||
AppRoutingModule.forChild(appRoutes),
|
||||
CoreMainMenuMoreRoutingModule.forChild({ siblings: mainMenuMoreRoutes }),
|
||||
CoreMainMenuTabRoutingModule.forChild({ siblings: mainMenuMoreRoutes }),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
|
|
@ -16,10 +16,10 @@ import { Injectable } from '@angular/core';
|
|||
import { Params } from '@angular/router';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreSiteHome } from '../sitehome';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
|
||||
/**
|
||||
* Handler to treat links to site home index.
|
||||
|
@ -43,7 +43,7 @@ export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandler
|
|||
getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreContentLinksHelper.instance.goInSite('sitehome', [], siteId);
|
||||
CoreNavHelper.instance.goInSite('sitehome', [], siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<!-- @todo <ng-container *ngIf="loaded">
|
||||
<ng-container *ngIf="loaded">
|
||||
<core-dynamic-component [component]="areaComponent" [data]="{items: items}"></core-dynamic-component>
|
||||
</ng-container>-->
|
||||
</ng-container>
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError">
|
||||
</core-infinite-loading>
|
||||
</core-loading>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Type } from '@angular/core';
|
||||
import { IonInfiniteScroll, IonRefresher } from '@ionic/angular';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTag } from '@features/tag/services/tag';
|
||||
|
@ -20,6 +20,7 @@ import { CoreTagFeedElement } from '../../services/tag-helper';
|
|||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CoreTagAreaDelegate } from '../../services/tag-area-delegate';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Page that displays the tag index area.
|
||||
|
@ -47,7 +48,7 @@ export class CoreTagIndexAreaPage implements OnInit {
|
|||
items: CoreTagFeedElement[] = [];
|
||||
nextPage = 0;
|
||||
canLoadMore = false;
|
||||
areaComponent: any; // @todo
|
||||
areaComponent?: Type<unknown>;
|
||||
loadMoreError = false;
|
||||
|
||||
constructor(
|
||||
|
@ -59,7 +60,7 @@ export class CoreTagIndexAreaPage implements OnInit {
|
|||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
|
||||
const navParams = this.route.snapshot.queryParamMap;
|
||||
const navParams = this.route.snapshot.queryParams;
|
||||
|
||||
this.tagId = navParams['tagId'] ? parseInt(navParams['tagId'], 10) : this.tagId;
|
||||
this.tagName = navParams['tagName'] || this.tagName;
|
||||
|
@ -74,8 +75,8 @@ export class CoreTagIndexAreaPage implements OnInit {
|
|||
this.componentName = navParams['componentName'];
|
||||
this.itemType = navParams['itemType'];
|
||||
this.items = []; // @todo navParams['items'] || [];
|
||||
this.nextPage = navParams.has('nextPage') ? parseInt(navParams['nextPage']!, 10) : 0;
|
||||
this.canLoadMore = !!navParams['canLoadMore'];
|
||||
this.nextPage = typeof navParams['nextPage'] != 'undefined' ? parseInt(navParams['nextPage'], 10) : 0;
|
||||
this.canLoadMore = CoreUtils.instance.isTrueOrOne(navParams['canLoadMore']);
|
||||
|
||||
try {
|
||||
if (!this.componentName || !this.itemType || !this.items.length || this.nextPage == 0) {
|
||||
|
|
|
@ -168,8 +168,11 @@ export class CoreTagIndexPage implements OnInit {
|
|||
canLoadMore: area.canLoadMore,
|
||||
nextPage: 1,
|
||||
};
|
||||
// this.splitviewCtrl.push('core-tag-index-area', params);
|
||||
this.router.navigate(['core-tag-index-area'], { queryParams: params });
|
||||
// this.splitviewCtrl.push('index-area', params);
|
||||
this.router.navigate(['../index-area'], {
|
||||
queryParams: params,
|
||||
relativeTo: this.route,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { Params } from '@angular/router';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreTag } from '../tag';
|
||||
|
||||
|
@ -57,11 +57,11 @@ export class CoreTagIndexLinkHandlerService extends CoreContentLinksHandlerBase
|
|||
};
|
||||
|
||||
if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) {
|
||||
CoreContentLinksHelper.instance.goInSite('/main/tag/search', {}, siteId);
|
||||
CoreNavHelper.instance.goInSite('/tag/search', {}, siteId);
|
||||
} else if (pageParams.areaId) {
|
||||
CoreContentLinksHelper.instance.goInSite('/main/tag/index-area', pageParams, siteId);
|
||||
CoreNavHelper.instance.goInSite('/tag/index-area', pageParams, siteId);
|
||||
} else {
|
||||
CoreContentLinksHelper.instance.goInSite('/main/tag/index', pageParams, siteId);
|
||||
CoreNavHelper.instance.goInSite('/tag/index', pageParams, siteId);
|
||||
}
|
||||
},
|
||||
}];
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { Params } from '@angular/router';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreTag } from '../tag';
|
||||
|
||||
|
@ -47,7 +47,7 @@ export class CoreTagSearchLinkHandlerService extends CoreContentLinksHandlerBase
|
|||
query: params.query || '',
|
||||
};
|
||||
|
||||
CoreContentLinksHelper.instance.goInSite('/main/tag/search', pageParams, siteId);
|
||||
CoreNavHelper.instance.goInSite('/tag/search', pageParams, siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export class CoreTagAreaDelegateService extends CoreDelegate<CoreTagAreaHandler>
|
|||
protected handlerNameProperty = 'type';
|
||||
|
||||
constructor() {
|
||||
super('CoreTagAreaDelegate');
|
||||
super('CoreTagAreaDelegate', true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,31 +12,42 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Injector, NgModule } from '@angular/core';
|
||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'index',
|
||||
loadChildren: () => import('@features/tag/pages/index/index.page.module').then(m => m.CoreTagIndexPageModule),
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('@features/tag//pages/search/search.page.module').then(m => m.CoreTagSearchPageModule),
|
||||
},
|
||||
{
|
||||
path: 'index-area',
|
||||
loadChildren: () => import('@features/tag/pages/index-area/index-area.page.module').then(m => m.CoreTagIndexAreaPageModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'search',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
];
|
||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
return [
|
||||
{
|
||||
path: 'index',
|
||||
loadChildren: () => import('@features/tag/pages/index/index.page.module').then(m => m.CoreTagIndexPageModule),
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('@features/tag//pages/search/search.page.module').then(m => m.CoreTagSearchPageModule),
|
||||
},
|
||||
{
|
||||
path: 'index-area',
|
||||
loadChildren: () =>
|
||||
import('@features/tag/pages/index-area/index-area.page.module').then(m => m.CoreTagIndexAreaPageModule),
|
||||
},
|
||||
...buildTabMainRoutes(injector, {
|
||||
redirectTo: 'search',
|
||||
pathMatch: 'full',
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ROUTES,
|
||||
multi: true,
|
||||
deps: [Injector],
|
||||
useFactory: buildRoutes,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CoreTagLazyModule { }
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
// (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 { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileField } from '@features/user/services/user';
|
||||
|
||||
/**
|
||||
* Base class for components to render a user profile field.
|
||||
*/
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
export abstract class CoreUserProfileFieldBaseComponent implements OnInit {
|
||||
|
||||
@Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
|
||||
@Input() edit = false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled = false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
@Input() contextLevel?: string; // The context level.
|
||||
@Input() contextInstanceId?: number; // The instance ID related to the context.
|
||||
@Input() courseId?: number; // The course the field belongs to (if any).
|
||||
|
||||
control?: FormControl;
|
||||
modelName = '';
|
||||
value?: string;
|
||||
required?: boolean;
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (!this.field) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.edit && 'value' in this.field) {
|
||||
this.initForNonEdit(this.field);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.edit && 'required' in this.field) {
|
||||
this.initForEdit(this.field);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed without editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForNonEdit(field: CoreUserProfileField): void {
|
||||
this.value = field.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the data when the field is meant to be displayed for editing.
|
||||
*
|
||||
* @param field Field to render.
|
||||
*/
|
||||
protected initForEdit(field: AuthEmailSignupProfileField): void {
|
||||
this.modelName = 'profile_field_' + field.shortname;
|
||||
this.required = !!field.required;
|
||||
|
||||
this.control = this.createFormControl(field);
|
||||
this.form?.addControl(this.modelName, this.control);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Form control.
|
||||
*
|
||||
* @return Form control.
|
||||
*/
|
||||
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
|
||||
const formData = {
|
||||
value: field.defaultdata,
|
||||
disabled: this.disabled,
|
||||
};
|
||||
|
||||
return new FormControl(formData, this.required && !field.locked ? Validators.required : null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field';
|
||||
import { CoreUserTagAreaComponent } from './tag-area/tag-area';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreUserProfileFieldComponent,
|
||||
CoreUserTagAreaComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: [
|
||||
CoreUserProfileFieldComponent,
|
||||
CoreUserTagAreaComponent,
|
||||
],
|
||||
})
|
||||
export class CoreUserComponentsModule {}
|
|
@ -0,0 +1,6 @@
|
|||
<ion-item class="ion-text-wrap" *ngFor="let item of items" core-user-link [userId]="item.user.id">
|
||||
<core-user-avatar [user]="item.user" slot="start"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ item.heading }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
|
@ -12,22 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { InjectionToken, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { ModuleRoutes } from '@/app/app-routing.module';
|
||||
import { CoreUserTagFeedElement } from '@features/user/services/handlers/tag-area-handler';
|
||||
|
||||
export const MAIN_MENU_MORE_ROUTES = new InjectionToken('MAIN_MENU_MORE_ROUTES');
|
||||
/**
|
||||
* Component to render the user tag area.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-user-tag-area',
|
||||
templateUrl: 'core-user-tag-area.html',
|
||||
})
|
||||
export class CoreUserTagAreaComponent {
|
||||
|
||||
@NgModule()
|
||||
export class CoreMainMenuMoreRoutingModule {
|
||||
|
||||
static forChild(routes: Partial<ModuleRoutes>): ModuleWithProviders<CoreMainMenuMoreRoutingModule> {
|
||||
return {
|
||||
ngModule: CoreMainMenuMoreRoutingModule,
|
||||
providers: [
|
||||
{ provide: MAIN_MENU_MORE_ROUTES, multi: true, useValue: routes },
|
||||
],
|
||||
};
|
||||
}
|
||||
@Input() items?: CoreUserTagFeedElement[]; // Area items to render.
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
|
@ -0,0 +1,78 @@
|
|||
// (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 { Component, Input, OnInit, Type } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-user-profile-field',
|
||||
templateUrl: 'core-user-profile-field.html',
|
||||
})
|
||||
export class CoreUserProfileFieldComponent implements OnInit {
|
||||
|
||||
@Input() field?: AuthEmailSignupProfileField; // The profile field to be rendered.
|
||||
@Input() signup = false; // True if editing the field in signup. Defaults to false.
|
||||
@Input() edit = false; // True if editing the field. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
|
||||
@Input() registerAuth?: string; // Register auth method. E.g. 'email'.
|
||||
@Input() contextLevel?: string; // The context level.
|
||||
@Input() contextInstanceId?: number; // The instance ID related to the context.
|
||||
@Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters.
|
||||
|
||||
componentClass?: Type<unknown>; // The class of the component to render.
|
||||
data: CoreUserProfileFieldComponentData = {}; // Data to pass to the component.
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (!this.field) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.componentClass = await CoreUserProfileFieldDelegate.instance.getComponent(this.field, this.signup);
|
||||
|
||||
this.data.field = this.field;
|
||||
this.data.edit = CoreUtils.instance.isTrueOrOne(this.edit);
|
||||
if (this.edit) {
|
||||
this.data.signup = CoreUtils.instance.isTrueOrOne(this.signup);
|
||||
this.data.disabled = CoreUtils.instance.isTrueOrOne(this.field.locked);
|
||||
this.data.form = this.form;
|
||||
this.data.registerAuth = this.registerAuth;
|
||||
this.data.contextLevel = this.contextLevel;
|
||||
this.data.contextInstanceId = this.contextInstanceId;
|
||||
this.data.courseId = this.courseId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CoreUserProfileFieldComponentData = {
|
||||
field?: AuthEmailSignupProfileField;
|
||||
edit?: boolean;
|
||||
signup?: boolean;
|
||||
disabled?: boolean;
|
||||
form?: FormGroup;
|
||||
registerAuth?: string;
|
||||
contextLevel?: string;
|
||||
contextInstanceId?: number;
|
||||
courseId?: number;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"address": "Address",
|
||||
"city": "City/town",
|
||||
"contact": "Contact",
|
||||
"country": "Country",
|
||||
"description": "Description",
|
||||
"details": "Details",
|
||||
"detailsnotavailable": "The details of this user are not available to you.",
|
||||
"editingteacher": "Teacher",
|
||||
"email": "Email address",
|
||||
"emailagain": "Email (again)",
|
||||
"errorloaduser": "Error loading user.",
|
||||
"firstname": "First name",
|
||||
"interests": "Interests",
|
||||
"lastname": "Surname",
|
||||
"manager": "Manager",
|
||||
"newpicture": "New picture",
|
||||
"noparticipants": "No participants found for this course",
|
||||
"participants": "Participants",
|
||||
"phone1": "Phone",
|
||||
"phone2": "Mobile phone",
|
||||
"roles": "Roles",
|
||||
"sendemail": "Email",
|
||||
"student": "Student",
|
||||
"teacher": "Non-editing teacher",
|
||||
"webpage": "Web page"
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="title">{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!userLoaded" (ionRefresh)="refreshUser($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="userLoaded">
|
||||
<ion-list *ngIf="user">
|
||||
<ion-item-group *ngIf="hasContact">
|
||||
<ion-item-divider>{{ 'core.user.contact' | translate}}</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.email">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.email' | translate }}</h2>
|
||||
<p><a class="core-anchor" href="mailto:{{user.email}}" core-link auto-login="no">
|
||||
{{ user.email }}
|
||||
</a></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.phone1">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.phone1' | translate}}</h2>
|
||||
<p><a class="core-anchor" href="tel:{{user.phone1}}" core-link auto-login="no">
|
||||
{{ user.phone1 }}
|
||||
</a></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.phone2">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.phone2' | translate}}</h2>
|
||||
<p><a class="core-anchor" href="tel:{{user.phone2}}" core-link auto-login="no">
|
||||
{{ user.phone2 }}
|
||||
</a></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.address">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.address' | translate}}</h2>
|
||||
<p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no">
|
||||
{{ user.address }}
|
||||
</a></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.city && !user.address">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.city' | translate}}</h2>
|
||||
<p>{{ user.city }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.country && !user.address">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.country' | translate}}</h2>
|
||||
<p>{{ user.country }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
<ion-item-group *ngIf="hasDetails">
|
||||
<ion-item-divider>{{ 'core.userdetails' | translate}}</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.url">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.webpage' | translate}}</h2>
|
||||
<p><a class="core-anchor" href="{{user.url}}" core-link>
|
||||
{{ user.url }}
|
||||
</a></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="user.interests">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.interests' | translate}}</h2>
|
||||
<p>{{ user.interests }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<core-user-profile-field *ngFor="let field of user.customfields" [field]="field" contextLevel="course"
|
||||
[contextInstanceId]="courseId" [courseId]="courseId">
|
||||
</core-user-profile-field>
|
||||
</ion-item-group>
|
||||
<ion-item-group *ngIf="user.description">
|
||||
<ion-item-divider>{{ 'core.user.description' | translate}}</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p><core-format-text [text]="user.description" contextLevel="user" [contextInstanceId]="user.id">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-list>
|
||||
|
||||
<core-empty-box *ngIf="!user || (!hasContact && !hasDetails && !user.description)" icon="fa-user"
|
||||
[message]=" 'core.user.detailsnotavailable' | translate">
|
||||
</core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,49 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreUserComponentsModule } from '@features/user/components/components.module';
|
||||
|
||||
import { CoreUserAboutPage } from './about.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreUserAboutPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreUserComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreUserAboutPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreUserAboutPageModule {}
|
|
@ -0,0 +1,115 @@
|
|||
// (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 { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SafeUrl } from '@angular/platform-browser';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreUser, CoreUserProfile, CoreUserProfileRefreshedData, CoreUserProvider } from '@features/user/services/user';
|
||||
import { CoreUserHelper } from '@features/user/services/user-helper';
|
||||
|
||||
/**
|
||||
* Page that displays info about a user.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-user-about',
|
||||
templateUrl: 'about.html',
|
||||
})
|
||||
export class CoreUserAboutPage implements OnInit {
|
||||
|
||||
protected userId!: number;
|
||||
protected siteId: string;
|
||||
|
||||
courseId!: number;
|
||||
userLoaded = false;
|
||||
hasContact = false;
|
||||
hasDetails = false;
|
||||
user?: CoreUserProfile;
|
||||
title?: string;
|
||||
formattedAddress?: string;
|
||||
encodedAddress?: SafeUrl;
|
||||
|
||||
constructor(
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
this.siteId = CoreSites.instance.getCurrentSiteId();
|
||||
}
|
||||
|
||||
/**
|
||||
* On init.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.userId = this.route.snapshot.queryParams['userId'];
|
||||
this.courseId = this.route.snapshot.queryParams['courseId'];
|
||||
|
||||
this.fetchUser().finally(() => {
|
||||
this.userLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the user data.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async fetchUser(): Promise<void> {
|
||||
try {
|
||||
const user = await CoreUser.instance.getProfile(this.userId, this.courseId);
|
||||
|
||||
if (user.address) {
|
||||
this.formattedAddress = CoreUserHelper.instance.formatAddress(user.address, user.city, user.country);
|
||||
this.encodedAddress = CoreTextUtils.instance.buildAddressURL(user.address);
|
||||
}
|
||||
|
||||
this.hasContact = !!(user.email || user.phone1 || user.phone2 || user.city || user.country || user.address);
|
||||
this.hasDetails = !!(user.url || user.interests || (user.customfields && user.customfields.length > 0));
|
||||
|
||||
this.user = user;
|
||||
this.title = user.fullname;
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.user.errorloaduser', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the user data.
|
||||
*
|
||||
* @param event Event.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async refreshUser(event?: CustomEvent<IonRefresher>): Promise<void> {
|
||||
await CoreUtils.instance.ignoreErrors(CoreUser.instance.invalidateUserCache(this.userId));
|
||||
|
||||
await this.fetchUser();
|
||||
|
||||
event?.detail.complete();
|
||||
|
||||
if (this.user) {
|
||||
CoreEvents.trigger<CoreUserProfileRefreshedData>(CoreUserProvider.PROFILE_REFRESHED, {
|
||||
courseId: this.courseId,
|
||||
userId: this.userId,
|
||||
user: this.user,
|
||||
}, this.siteId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="title">{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!userLoaded" (ionRefresh)="refreshUser($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="userLoaded">
|
||||
<ion-list *ngIf="user && !isDeleted && isEnrolled">
|
||||
<ion-item class="ion-text-center core-user-profile-maininfo">
|
||||
<core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false" [checkOnline]="true">
|
||||
<div class="core-icon-foreground">
|
||||
<ion-icon *ngIf="canChangeProfilePicture" name="fa-pen" (click)="changeProfilePicture()">
|
||||
</ion-icon>
|
||||
</div>
|
||||
</core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ user.fullname }}</h2>
|
||||
<p *ngIf="user.address">{{ user.address }}</p>
|
||||
<p *ngIf="rolesFormatted" class="ion-text-wrap">
|
||||
<strong>{{ 'core.user.roles' | translate}}</strong>{{'core.labelsep' | translate}}
|
||||
{{ rolesFormatted }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-grid class="core-user-communication-handlers"
|
||||
*ngIf="(communicationHandlers && communicationHandlers.length) || isLoadingHandlers">
|
||||
<ion-row class="ion-no-padding justify-content-between"
|
||||
*ngIf="communicationHandlers && communicationHandlers.length">
|
||||
<ion-col *ngFor="let handler of communicationHandlers" class="ion-align-self-center ion-text-center">
|
||||
<a (click)="handlerClicked($event, handler)" [ngClass]="['core-user-profile-handler', handler.class]"
|
||||
title="{{handler.title | translate}}">
|
||||
<ion-icon [name]="handler.icon" slot="start"></ion-icon>
|
||||
<p>{{handler.title | translate}}</p>
|
||||
</a>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row class="ion-no-padding">
|
||||
<ion-col class="ion-text-center core-loading-handlers" *ngIf="isLoadingHandlers">
|
||||
<ion-spinner></ion-spinner>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
<ion-item button class="ion-text-wrap core-user-profile-handler" (click)="openUserDetails()"
|
||||
title="{{ 'core.user.details' | translate }}">
|
||||
<ion-icon name="fa-user" slot="start"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ 'core.user.details' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-center core-loading-handlers" *ngIf="isLoadingHandlers">
|
||||
<ion-spinner></ion-spinner>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button *ngFor="let handler of newPageHandlers" class="ion-text-wrap" (click)="handlerClicked($event, handler)"
|
||||
[ngClass]="['core-user-profile-handler', handler.class]" [hidden]="handler.hidden"
|
||||
title="{{ handler.title | translate }}">
|
||||
<ion-label>
|
||||
<ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start"></ion-icon>
|
||||
<h2>{{ handler.title | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="actionHandlers && actionHandlers.length">
|
||||
<ion-button *ngFor="let handler of actionHandlers" expand="block" fill="outline"
|
||||
[ngClass]="['core-user-profile-handler', handler.class]" (click)="handlerClicked($event, handler)"
|
||||
[hidden]="handler.hidden" title="{{ handler.title | translate }}" [disabled]="handler.spinner">
|
||||
<ion-label>
|
||||
<ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start"></ion-icon>
|
||||
<span>{{ handler.title | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-spinner *ngIf="handler.spinner"></ion-spinner>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<core-empty-box *ngIf="!user && !isDeleted && isEnrolled" icon="fa-user"
|
||||
[message]=" 'core.user.detailsnotavailable' | translate">
|
||||
</core-empty-box>
|
||||
<core-empty-box *ngIf="isDeleted" icon="fa-user" [message]="'core.userdeleted' | translate"></core-empty-box>
|
||||
<core-empty-box *ngIf="!isEnrolled" icon="fa-user" [message]="'core.notenrolledprofile' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,47 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
import { CoreUserProfilePage } from './profile.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreUserProfilePage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreUserProfilePage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreUserProfilePageModule {}
|
|
@ -0,0 +1,288 @@
|
|||
// (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 { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { IonRefresher, NavController } from '@ionic/angular';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import {
|
||||
CoreUser,
|
||||
CoreUserProfile,
|
||||
CoreUserProfilePictureUpdatedData,
|
||||
CoreUserProfileRefreshedData,
|
||||
CoreUserProvider,
|
||||
} from '@features/user/services/user';
|
||||
import { CoreUserHelper } from '@features/user/services/user-helper';
|
||||
import { CoreUserDelegate, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||
import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
|
||||
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'page-core-user-profile',
|
||||
templateUrl: 'profile.html',
|
||||
styleUrls: ['profile.scss'],
|
||||
})
|
||||
export class CoreUserProfilePage implements OnInit, OnDestroy {
|
||||
|
||||
protected courseId!: number;
|
||||
protected userId!: number;
|
||||
protected site?: CoreSite;
|
||||
protected obsProfileRefreshed: CoreEventObserver;
|
||||
protected subscription?: Subscription;
|
||||
|
||||
userLoaded = false;
|
||||
isLoadingHandlers = false;
|
||||
user?: CoreUserProfile;
|
||||
title?: string;
|
||||
isDeleted = false;
|
||||
isEnrolled = true;
|
||||
canChangeProfilePicture = false;
|
||||
rolesFormatted?: string;
|
||||
actionHandlers: CoreUserProfileHandlerData[] = [];
|
||||
newPageHandlers: CoreUserProfileHandlerData[] = [];
|
||||
communicationHandlers: CoreUserProfileHandlerData[] = [];
|
||||
|
||||
constructor(
|
||||
protected route: ActivatedRoute,
|
||||
protected navCtrl: NavController,
|
||||
) {
|
||||
|
||||
this.obsProfileRefreshed = CoreEvents.on<CoreUserProfileRefreshedData>(CoreUserProvider.PROFILE_REFRESHED, (data) => {
|
||||
if (!this.user || !data.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.user.email = data.user.email;
|
||||
this.user.address = CoreUserHelper.instance.formatAddress('', data.user.city, data.user.country);
|
||||
}, CoreSites.instance.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* On init.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.site = CoreSites.instance.getCurrentSite();
|
||||
this.userId = this.route.snapshot.queryParams['userId'];
|
||||
this.courseId = this.route.snapshot.queryParams['courseId'];
|
||||
|
||||
if (!this.site) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow to change the profile image only in the app profile page.
|
||||
this.canChangeProfilePicture =
|
||||
(!this.courseId || this.courseId == this.site.getSiteHomeId()) &&
|
||||
this.userId == this.site.getUserId() &&
|
||||
this.site.canUploadFiles() &&
|
||||
CoreUser.instance.canUpdatePictureInSite(this.site) &&
|
||||
!CoreUser.instance.isUpdatePictureDisabledInSite(this.site);
|
||||
|
||||
try {
|
||||
await this.fetchUser();
|
||||
|
||||
try {
|
||||
await CoreUser.instance.logView(this.userId, this.courseId, this.user!.fullname);
|
||||
} catch (error) {
|
||||
this.isDeleted = error?.errorcode === 'userdeleted';
|
||||
this.isEnrolled = error?.errorcode !== 'notenrolledprofile';
|
||||
}
|
||||
} finally {
|
||||
this.userLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the user and updates the view.
|
||||
*/
|
||||
async fetchUser(): Promise<void> {
|
||||
try {
|
||||
const user = await CoreUser.instance.getProfile(this.userId, this.courseId);
|
||||
|
||||
user.address = CoreUserHelper.instance.formatAddress('', user.city, user.country);
|
||||
this.rolesFormatted = 'roles' in user ? CoreUserHelper.instance.formatRoleList(user.roles) : '';
|
||||
|
||||
this.user = user;
|
||||
this.title = user.fullname;
|
||||
|
||||
// If there's already a subscription, unsubscribe because we'll get a new one.
|
||||
this.subscription?.unsubscribe();
|
||||
|
||||
this.subscription = CoreUserDelegate.instance.getProfileHandlersFor(user, this.courseId).subscribe((handlers) => {
|
||||
this.actionHandlers = [];
|
||||
this.newPageHandlers = [];
|
||||
this.communicationHandlers = [];
|
||||
handlers.forEach((handler) => {
|
||||
switch (handler.type) {
|
||||
case CoreUserDelegateService.TYPE_COMMUNICATION:
|
||||
this.communicationHandlers.push(handler.data);
|
||||
break;
|
||||
case CoreUserDelegateService.TYPE_ACTION:
|
||||
this.actionHandlers.push(handler.data);
|
||||
break;
|
||||
case CoreUserDelegateService.TYPE_NEW_PAGE:
|
||||
default:
|
||||
this.newPageHandlers.push(handler.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.isLoadingHandlers = !CoreUserDelegate.instance.areHandlersLoaded(user.id);
|
||||
});
|
||||
|
||||
await this.checkUserImageUpdated();
|
||||
|
||||
} catch (error) {
|
||||
// Error is null for deleted users, do not show the modal.
|
||||
CoreDomUtils.instance.showErrorModal(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current user image has changed.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async checkUserImageUpdated(): Promise<void> {
|
||||
if (!this.site || !this.site.getInfo() || !this.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.userId != this.site.getUserId() || this.user.profileimageurl == this.site.getInfo()!.userpictureurl) {
|
||||
// Not current user or hasn't changed.
|
||||
return;
|
||||
}
|
||||
|
||||
// The current user image received is different than the one stored in site info. Assume the image was updated.
|
||||
// Update the site info to get the right avatar in there.
|
||||
try {
|
||||
await CoreSites.instance.updateSiteInfo(this.site.getId());
|
||||
} catch {
|
||||
// Cannot update site info. Assume the profile image is the right one.
|
||||
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||
userId: this.userId,
|
||||
picture: this.user.profileimageurl,
|
||||
}, this.site.getId());
|
||||
}
|
||||
|
||||
if (this.user.profileimageurl != this.site.getInfo()!.userpictureurl) {
|
||||
// The image is still different, this means that the good one is the one in site info.
|
||||
await this.refreshUser();
|
||||
} else {
|
||||
// Now they're the same, send event to use the right avatar in the rest of the app.
|
||||
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||
userId: this.userId,
|
||||
picture: this.user.profileimageurl,
|
||||
}, this.site.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens dialog to change profile picture.
|
||||
*/
|
||||
async changeProfilePicture(): Promise<void> {
|
||||
const maxSize = -1;
|
||||
const title = Translate.instance.instant('core.user.newpicture');
|
||||
const mimetypes = CoreMimetypeUtils.instance.getGroupMimeInfo('image', 'mimetypes');
|
||||
let modal: CoreIonLoadingElement | undefined;
|
||||
|
||||
try {
|
||||
const result = await CoreFileUploaderHelper.instance.selectAndUploadFile(maxSize, title, mimetypes);
|
||||
|
||||
modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
|
||||
|
||||
const profileImageURL = await CoreUser.instance.changeProfilePicture(result.itemid, this.userId, this.site!.getId());
|
||||
|
||||
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||
userId: this.userId,
|
||||
picture: profileImageURL,
|
||||
}, this.site!.getId());
|
||||
|
||||
CoreSites.instance.updateSiteInfo(this.site!.getId());
|
||||
|
||||
this.refreshUser();
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModal(error);
|
||||
} finally {
|
||||
modal?.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the user.
|
||||
*
|
||||
* @param event Event.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async refreshUser(event?: CustomEvent<IonRefresher>): Promise<void> {
|
||||
await CoreUtils.instance.ignoreErrors(Promise.all([
|
||||
CoreUser.instance.invalidateUserCache(this.userId),
|
||||
// @todo this.coursesProvider.invalidateUserNavigationOptions(),
|
||||
// this.coursesProvider.invalidateUserAdministrationOptions()
|
||||
]));
|
||||
|
||||
await this.fetchUser();
|
||||
|
||||
event?.detail.complete();
|
||||
|
||||
if (this.user) {
|
||||
CoreEvents.trigger<CoreUserProfileRefreshedData>(CoreUserProvider.PROFILE_REFRESHED, {
|
||||
courseId: this.courseId,
|
||||
userId: this.userId,
|
||||
user: this.user,
|
||||
}, this.site?.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the page with the user details.
|
||||
*/
|
||||
openUserDetails(): void {
|
||||
// @todo: Navigate out of split view if this page is in the right pane.
|
||||
this.navCtrl.navigateForward(['../about'], {
|
||||
relativeTo: this.route,
|
||||
queryParams: {
|
||||
courseId: this.courseId,
|
||||
userId: this.userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler was clicked.
|
||||
*
|
||||
* @param event Click event.
|
||||
* @param handler Handler that was clicked.
|
||||
*/
|
||||
handlerClicked(event: Event, handler: CoreUserProfileHandlerData): void {
|
||||
// @todo: Pass the right navCtrl if this page is in the right pane of split view.
|
||||
handler.action(event, this.user!, this.courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe();
|
||||
this.obsProfileRefreshed.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
:host {
|
||||
|
||||
.core-user-profile-maininfo::part(native) {
|
||||
flex-direction: column;
|
||||
}
|
||||
::ng-deep {
|
||||
core-user-avatar {
|
||||
display: block;
|
||||
--core-avatar-size: var(--core-large-avatar-size);
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.contact-status {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
.core-icon-foreground {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color:var(--background);
|
||||
|
||||
:host-context([dir="rtl"]) & {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
:host-context([dir="rtl"]) ::ng-deep core-user-avatar .core-icon-foreground {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
// @todo
|
||||
// .core-user-communication-handlers {
|
||||
// background: $list-background-color;
|
||||
// border-bottom: 1px solid $list-border-color;
|
||||
|
||||
// @include darkmode() {
|
||||
// background: $core-dark-item-bg-color;
|
||||
// }
|
||||
|
||||
// .core-user-profile-handler {
|
||||
// background: $list-background-color;
|
||||
// border: 0;
|
||||
// color: $core-user-profile-communication-icons-color;
|
||||
|
||||
// @include darkmode() {
|
||||
// background: $core-dark-item-bg-color;
|
||||
// }
|
||||
|
||||
// p {
|
||||
// margin: 0;
|
||||
// }
|
||||
|
||||
// .icon {
|
||||
// border-radius: 50%;
|
||||
// width: 32px;
|
||||
// height: 32px;
|
||||
// max-width: 32px;
|
||||
// font-size: 22px;
|
||||
// line-height: 32px;
|
||||
// color: white;
|
||||
// background-color: $core-user-profile-communication-icons-color;
|
||||
// margin-bottom: 5px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .core-user-profile-handler {
|
||||
// ion-spinner {
|
||||
// @include margin(null, null, null, 0.3em);
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// (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 { CoreSiteSchema } from '@services/sites';
|
||||
import { CoreUserBasicData } from '../user';
|
||||
|
||||
/**
|
||||
* Database variables for CoreUser service.
|
||||
*/
|
||||
export const USERS_TABLE_NAME = 'users';
|
||||
export const SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'CoreUserProvider',
|
||||
version: 1,
|
||||
canBeCleared: [USERS_TABLE_NAME],
|
||||
tables: [
|
||||
{
|
||||
name: USERS_TABLE_NAME,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'fullname',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'profileimageurl',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Database variables for CoreUserOffline service.
|
||||
*/
|
||||
export const PREFERENCES_TABLE_NAME = 'user_preferences';
|
||||
export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'CoreUserOfflineProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: PREFERENCES_TABLE_NAME,
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'TEXT',
|
||||
unique: true,
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'onlinevalue',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Data stored in DB for users.
|
||||
*/
|
||||
export type CoreUserDBRecord = CoreUserBasicData;
|
||||
|
||||
/**
|
||||
* Structure of offline user preferences.
|
||||
*/
|
||||
export type CoreUserPreferenceDBRecord = {
|
||||
name: string;
|
||||
value: string;
|
||||
onlinevalue: string;
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavHelper } from '@services/nav-helper';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to treat links to user profiles.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserProfileLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'CoreUserProfileLinkHandler';
|
||||
// Match user/view.php and user/profile.php but NOT grade/report/user/.
|
||||
pattern = /((\/user\/view\.php)|(\/user\/profile\.php)).*([?&]id=\d+)/;
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param siteIds List of sites the URL belongs to.
|
||||
* @param url The URL to treat.
|
||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||
* @param data Extra data to handle the URL.
|
||||
* @return List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(
|
||||
siteIds: string[],
|
||||
url: string,
|
||||
params: Params,
|
||||
courseId?: number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
data?: unknown, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: (siteId): void => {
|
||||
const pageParams = {
|
||||
courseId: params.course,
|
||||
userId: parseInt(params.id, 10),
|
||||
};
|
||||
|
||||
CoreNavHelper.instance.goInSite('/user', pageParams, siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param siteId The site ID.
|
||||
* @param url The URL to treat.
|
||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||
* @return Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isEnabled(siteId: string, url: string, params: Params, courseId?: number): boolean | Promise<boolean> {
|
||||
return url.indexOf('/grade/report/') == -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserProfileLinkHandler extends makeSingleton(CoreUserProfileLinkHandlerService) {}
|
|
@ -0,0 +1,78 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../user-delegate';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreUserProfile } from '../user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to send a email to a user.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler {
|
||||
|
||||
name = 'CoreUserProfileMail';
|
||||
priority = 700;
|
||||
type = CoreUserDelegateService.TYPE_COMMUNICATION;
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return Always enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @param user User to check.
|
||||
* @param courseId Course ID.
|
||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return Promise resolved with true if enabled, resolved with false otherwise.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean> {
|
||||
return user.id != CoreSites.instance.getCurrentSiteUserId() && !!user.email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return Data needed to render the handler.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
|
||||
return {
|
||||
icon: 'mail',
|
||||
title: 'core.user.sendemail',
|
||||
class: 'core-user-profile-mail',
|
||||
action: (event: Event, user: CoreUserProfile): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
CoreUtils.instance.openInBrowser('mailto:' + user.email);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserProfileMailHandler extends makeSingleton(CoreUserProfileMailHandlerService) {}
|
|
@ -0,0 +1,53 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreCronHandler } from '@services/cron';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreUserSync } from '../user-sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserSyncCronHandlerService implements CoreCronHandler {
|
||||
|
||||
name = 'CoreUserSyncCronHandler';
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param siteId ID of the site affected, undefined for all sites.
|
||||
* @param force Wether the execution is forced (manual sync).
|
||||
* @return Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
execute(siteId?: string, force?: boolean): Promise<void> {
|
||||
return CoreUserSync.instance.syncPreferences(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time between consecutive executions.
|
||||
*
|
||||
* @return Time between consecutive executions (in ms).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return 300000; // 5 minutes.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserSyncCronHandler extends makeSingleton(CoreUserSyncCronHandlerService) {}
|
|
@ -0,0 +1,98 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
|
||||
import { CoreUserTagAreaComponent } from '@features/user/components/tag-area/tag-area';
|
||||
import { CoreTagFeedElement } from '@features/tag/services/tag-helper';
|
||||
import { CoreUserBasicData } from '../user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to support tags.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserTagAreaHandlerService implements CoreTagAreaHandler {
|
||||
|
||||
name = 'CoreUserTagAreaHandler';
|
||||
type = 'core/user';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the rendered content of a tag index and returns the items.
|
||||
*
|
||||
* @param content Rendered content.
|
||||
* @return Area items (or promise resolved with the items).
|
||||
*/
|
||||
parseContent(content: string): CoreUserTagFeedElement[] {
|
||||
const items: CoreUserTagFeedElement[] = [];
|
||||
const element = CoreDomUtils.instance.convertToElement(content);
|
||||
|
||||
Array.from(element.querySelectorAll('div.user-box')).forEach((userbox: HTMLElement) => {
|
||||
const avatarLink = userbox.querySelector('a:first-child');
|
||||
if (!avatarLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
const profileUrl = avatarLink.getAttribute('href') || '';
|
||||
const match = profileUrl.match(/.*\/user\/(?:profile|view)\.php\?id=(\d+)/);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const avatarImg = avatarLink.querySelector('img.userpicture');
|
||||
const avatarUrl = avatarImg ? avatarImg.getAttribute('src') : '';
|
||||
|
||||
items.push({
|
||||
avatarUrl,
|
||||
heading: userbox.innerText,
|
||||
details: [],
|
||||
user: {
|
||||
id: Number(match[1]),
|
||||
profileimageurl: avatarUrl || '',
|
||||
fullname: userbox.innerText,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component to use to display items.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return CoreUserTagAreaComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserTagAreaHandler extends makeSingleton(CoreUserTagAreaHandlerService) {}
|
||||
|
||||
export type CoreUserTagFeedElement = CoreTagFeedElement & {
|
||||
user: CoreUserBasicData;
|
||||
};
|
|
@ -0,0 +1,282 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreUserProfile } from './user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Interface that all user profile handlers must implement.
|
||||
*/
|
||||
export interface CoreUserProfileHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* The highest priority is displayed first.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* A type should be specified among these:
|
||||
* - TYPE_COMMUNICATION: will be displayed under the user avatar. Should have icon. Spinner not used.
|
||||
* - TYPE_NEW_PAGE: will be displayed as a list of items. Should have icon. Spinner not used.
|
||||
* Default value if none is specified.
|
||||
* - TYPE_ACTION: will be displayed as a button and should not redirect to any state. Spinner use is recommended.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled for a user.
|
||||
*
|
||||
* @param user User object.
|
||||
* @param courseId Course ID where to show.
|
||||
* @param navOptions Navigation options for the course.
|
||||
* @param admOptions Admin options for the course.
|
||||
* @return Whether or not the handler is enabled for a user.
|
||||
*/
|
||||
isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @param user User object.
|
||||
* @param courseId Course ID where to show.
|
||||
* @return Data to be shown.
|
||||
*/
|
||||
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data needed to render a user profile handler. It's returned by the handler.
|
||||
*/
|
||||
export interface CoreUserProfileHandlerData {
|
||||
/**
|
||||
* Title to display.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Name of the icon to display. Mandatory for TYPE_COMMUNICATION.
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* Additional class to add to the HTML.
|
||||
*/
|
||||
class?: string;
|
||||
|
||||
/**
|
||||
* If enabled, element will be hidden. Only for TYPE_NEW_PAGE and TYPE_ACTION.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
|
||||
/**
|
||||
* If enabled will show an spinner. Only for TYPE_ACTION.
|
||||
*/
|
||||
spinner?: boolean;
|
||||
|
||||
/**
|
||||
* Action to do when clicked.
|
||||
*
|
||||
* @param event Click event.
|
||||
* @param user User object.
|
||||
* @param courseId Course ID being viewed. If not defined, site context.
|
||||
*/
|
||||
action(event: Event, user: CoreUserProfile, courseId?: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data returned by the delegate for each handler.
|
||||
*/
|
||||
export interface CoreUserProfileHandlerToDisplay {
|
||||
/**
|
||||
* Name of the handler.
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Data to display.
|
||||
*/
|
||||
data: CoreUserProfileHandlerData;
|
||||
|
||||
/**
|
||||
* The highest priority is displayed first.
|
||||
*/
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* The type of the handler. See CoreUserProfileHandler.
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to interact with plugins to be shown in user profile. Provides functions to register a plugin
|
||||
* and notify an update in the data.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler> {
|
||||
|
||||
/**
|
||||
* User profile handler type for communication.
|
||||
*/
|
||||
static readonly TYPE_COMMUNICATION = 'communication';
|
||||
/**
|
||||
* User profile handler type for new page.
|
||||
*/
|
||||
static readonly TYPE_NEW_PAGE = 'newpage';
|
||||
/**
|
||||
* User profile handler type for actions.
|
||||
*/
|
||||
static readonly TYPE_ACTION = 'action';
|
||||
|
||||
/**
|
||||
* Update handler information event.
|
||||
*/
|
||||
static readonly UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event';
|
||||
|
||||
protected featurePrefix = 'CoreUserDelegate_';
|
||||
|
||||
// Hold the handlers and the observable to notify them for each user.
|
||||
protected userHandlers: {
|
||||
[userId: number]: {
|
||||
loaded: boolean; // Whether the handlers are loaded.
|
||||
handlers: CoreUserProfileHandlerToDisplay[]; // List of handlers.
|
||||
observable: Subject<CoreUserProfileHandlerToDisplay[]>; // Observale to notify the handlers.
|
||||
};
|
||||
} = {};
|
||||
|
||||
constructor() {
|
||||
super('CoreUserDelegate', true);
|
||||
|
||||
CoreEvents.on<CoreUserUpdateHandlerData>(CoreUserDelegateService.UPDATE_HANDLER_EVENT, (data) => {
|
||||
if (!data || !data.handler || !this.userHandlers[data.userId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the handler.
|
||||
const handler = this.userHandlers[data.userId].handlers.find((userHandler) => userHandler.name == data.handler);
|
||||
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the data and notify.
|
||||
Object.assign(handler.data, data.data);
|
||||
this.userHandlers[data.userId].observable.next(this.userHandlers[data.userId].handlers);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handlers are loaded.
|
||||
*
|
||||
* @return True if handlers are loaded, false otherwise.
|
||||
*/
|
||||
areHandlersLoaded(userId: number): boolean {
|
||||
return this.userHandlers[userId]?.loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current user handlers.
|
||||
*
|
||||
* @param userId The user to clear.
|
||||
*/
|
||||
clearUserHandlers(userId: number): void {
|
||||
const userData = this.userHandlers[userId];
|
||||
|
||||
if (userData) {
|
||||
userData.handlers = [];
|
||||
userData.observable.next([]);
|
||||
userData.loaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profile handlers for a user.
|
||||
*
|
||||
* @param user The user object.
|
||||
* @param courseId The course ID.
|
||||
* @return Resolved with the handlers.
|
||||
*/
|
||||
getProfileHandlersFor(user: CoreUserProfile, courseId: number): Subject<CoreUserProfileHandlerToDisplay[]> {
|
||||
// Initialize the user handlers if it isn't initialized already.
|
||||
if (!this.userHandlers[user.id]) {
|
||||
this.userHandlers[user.id] = {
|
||||
loaded: false,
|
||||
handlers: [],
|
||||
observable: new BehaviorSubject<CoreUserProfileHandlerToDisplay[]>([]),
|
||||
};
|
||||
}
|
||||
|
||||
this.calculateUserHandlers(user, courseId);
|
||||
|
||||
return this.userHandlers[user.id].observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profile handlers for a user.
|
||||
*
|
||||
* @param user The user object.
|
||||
* @param courseId The course ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async calculateUserHandlers(user: CoreUserProfile, courseId: number): Promise<void> {
|
||||
// @todo: Get Course admin/nav options.
|
||||
let navOptions;
|
||||
let admOptions;
|
||||
|
||||
const userData = this.userHandlers[user.id];
|
||||
userData.handlers = [];
|
||||
|
||||
await CoreUtils.instance.allPromises(Object.keys(this.enabledHandlers).map(async (name) => {
|
||||
// Checks if the handler is enabled for the user.
|
||||
const handler = this.handlers[name];
|
||||
|
||||
try {
|
||||
const enabled = await handler.isEnabledForUser(user, courseId, navOptions, admOptions);
|
||||
|
||||
if (enabled) {
|
||||
userData.handlers.push({
|
||||
name: name,
|
||||
data: handler.getDisplayData(user, courseId),
|
||||
priority: handler.priority || 0,
|
||||
type: handler.type || CoreUserDelegateService.TYPE_NEW_PAGE,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Nothing to do here, it is not enabled for this user.
|
||||
}
|
||||
}));
|
||||
|
||||
// Sort them by priority.
|
||||
userData.handlers.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
userData.loaded = true;
|
||||
userData.observable.next(userData.handlers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserDelegate extends makeSingleton(CoreUserDelegateService) {}
|
||||
|
||||
/**
|
||||
* Data passed to UPDATE_HANDLER_EVENT event.
|
||||
*/
|
||||
export type CoreUserUpdateHandlerData = {
|
||||
handler: string; // Name of the handler.
|
||||
userId: number; // User affected.
|
||||
data: Record<string, unknown>; // Data to set to the handler.
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreUserRole } from './user';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding users information.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserHelperProvider {
|
||||
|
||||
/**
|
||||
* Formats a user address, concatenating address, city and country.
|
||||
*
|
||||
* @param address Address.
|
||||
* @param city City.
|
||||
* @param country Country.
|
||||
* @return Formatted address.
|
||||
*/
|
||||
formatAddress(address?: string, city?: string, country?: string): string {
|
||||
const separator = Translate.instance.instant('core.listsep');
|
||||
let values = [address, city, country];
|
||||
|
||||
values = values.filter((value) => value && value.length > 0);
|
||||
|
||||
return values.join(separator + ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a user role list, translating and concatenating them.
|
||||
*
|
||||
* @param roles List of user roles.
|
||||
* @return The formatted roles.
|
||||
*/
|
||||
formatRoleList(roles?: CoreUserRole[]): string {
|
||||
if (!roles || roles.length <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const separator = Translate.instance.instant('core.listsep');
|
||||
|
||||
return roles.map((value) => {
|
||||
const translation = Translate.instance.instant('core.user.' + value.shortname);
|
||||
|
||||
return translation.indexOf('core.user.') < 0 ? translation : value.shortname;
|
||||
}).join(separator + ' ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserHelper extends makeSingleton(CoreUserHelperProvider) {}
|
|
@ -0,0 +1,81 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { PREFERENCES_TABLE_NAME, CoreUserPreferenceDBRecord } from './database/user';
|
||||
|
||||
/**
|
||||
* Service to handle offline user preferences.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserOfflineProvider {
|
||||
|
||||
/**
|
||||
* Get preferences that were changed offline.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with list of preferences.
|
||||
*/
|
||||
async getChangedPreferences(siteId?: string): Promise<CoreUserPreferenceDBRecord[]> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return site.getDb().getRecordsSelect(PREFERENCES_TABLE_NAME, 'value != onlineValue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an offline preference.
|
||||
*
|
||||
* @param name Name of the preference.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the preference, rejected if not found.
|
||||
*/
|
||||
async getPreference(name: string, siteId?: string): Promise<CoreUserPreferenceDBRecord> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return site.getDb().getRecord(PREFERENCES_TABLE_NAME, { name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an offline preference.
|
||||
*
|
||||
* @param name Name of the preference.
|
||||
* @param value Value of the preference.
|
||||
* @param onlineValue Online value of the preference. If undefined, preserve previously stored value.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async setPreference(name: string, value: string, onlineValue?: string, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
if (typeof onlineValue == 'undefined') {
|
||||
const preference = await this.getPreference(name, site.id);
|
||||
|
||||
onlineValue = preference.onlinevalue;
|
||||
}
|
||||
|
||||
const record: CoreUserPreferenceDBRecord = {
|
||||
name,
|
||||
value,
|
||||
onlinevalue: onlineValue,
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(PREFERENCES_TABLE_NAME, record);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserOffline extends makeSingleton(CoreUserOfflineProvider) {}
|
|
@ -0,0 +1,209 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreUserProfileField } from './user';
|
||||
|
||||
/**
|
||||
* Interface that all user profile field handlers must implement.
|
||||
*/
|
||||
export interface CoreUserProfileFieldHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* Type of the field the handler supports. E.g. 'checkbox'.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>>;
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form Values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
getData?(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined>;
|
||||
}
|
||||
|
||||
export interface CoreUserProfileFieldHandlerData {
|
||||
/**
|
||||
* Name of the custom field.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The type of the custom field
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Value of the custom field.
|
||||
*/
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to interact with user profile fields.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUserProfileFieldDelegateService extends CoreDelegate<CoreUserProfileFieldHandler> {
|
||||
|
||||
protected handlerNameProperty = 'type';
|
||||
|
||||
constructor() {
|
||||
super('CoreUserProfileFieldDelegate', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of a field.
|
||||
*
|
||||
* @param field The field to get its type.
|
||||
* @return The field type.
|
||||
*/
|
||||
protected getType(field: AuthEmailSignupProfileField | CoreUserProfileField): string {
|
||||
return ('type' in field ? field.type : field.datatype) || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component to use to display an user field.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @param field User field to get the directive for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @return Promise resolved with component to use, undefined if not found.
|
||||
*/
|
||||
async getComponent(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
): Promise<Type<unknown> | undefined> {
|
||||
const type = this.getType(field);
|
||||
|
||||
try {
|
||||
if (signup) {
|
||||
return await this.executeFunction(type, 'getComponent', []);
|
||||
} else {
|
||||
return await this.executeFunctionOnEnabled(type, 'getComponent', []);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting component for field', type, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for a certain field based on the input data.
|
||||
*
|
||||
* @param field User field to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form values.
|
||||
* @return Data to send for the field.
|
||||
*/
|
||||
async getDataForField(
|
||||
field: AuthEmailSignupProfileField | CoreUserProfileField,
|
||||
signup: boolean,
|
||||
registerAuth: string,
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData | undefined> {
|
||||
const type = this.getType(field);
|
||||
const handler = this.getHandler(type, !signup);
|
||||
|
||||
if (handler) {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (handler.getData) {
|
||||
return await handler.getData(field, signup, registerAuth, formValues);
|
||||
} else if (field.shortname && typeof formValues[name] != 'undefined') {
|
||||
// Handler doesn't implement the function, but the form has data for the field.
|
||||
return {
|
||||
type: type,
|
||||
name: name,
|
||||
value: formValues[name],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new CoreError('User profile field handler not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for a list of fields based on the input data.
|
||||
*
|
||||
* @param fields User fields to get the data for.
|
||||
* @param signup True if user is in signup page.
|
||||
* @param registerAuth Register auth method. E.g. 'email'.
|
||||
* @param formValues Form values.
|
||||
* @return Data to send.
|
||||
*/
|
||||
async getDataForFields(
|
||||
fields: (AuthEmailSignupProfileField | CoreUserProfileField)[] | undefined,
|
||||
signup: boolean = false,
|
||||
registerAuth: string = '',
|
||||
formValues: Record<string, unknown>,
|
||||
): Promise<CoreUserProfileFieldHandlerData[]> {
|
||||
if (!fields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: CoreUserProfileFieldHandlerData[] = [];
|
||||
|
||||
await Promise.all(fields.map(async (field) => {
|
||||
try {
|
||||
const data = await this.getDataForField(field, signup, registerAuth, formValues);
|
||||
|
||||
if (data) {
|
||||
result.push(data);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the profile fields is not supported in the app.
|
||||
*
|
||||
* @param fields List of fields.
|
||||
* @return Whether any of the profile fields is not supported in the app.
|
||||
*/
|
||||
hasRequiredUnsupportedField(fields?: AuthEmailSignupProfileField[]): boolean {
|
||||
if (!fields || !fields.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fields.some((field) => field.required && !this.hasHandler(this.getType(field)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUserProfileFieldDelegate extends makeSingleton(CoreUserProfileFieldDelegateService) {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue