Merge pull request #2730 from dpalou/MOBILE-3734

Mobile 3734
main
Pau Ferrer Ocaña 2021-04-29 14:08:17 +02:00 committed by GitHub
commit 1abd630ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 351 additions and 249 deletions

View File

@ -41,6 +41,7 @@ import {
AddonModAssignSyncProvider,
AddonModAssignSyncResult,
} from '../../services/assign-sync';
import { AddonModAssignModuleHandlerService } from '../../services/handlers/module';
import { AddonModAssignSubmissionComponent } from '../submission/submission';
/**
@ -318,9 +319,13 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
if (typeof status != 'undefined') {
params.status = status;
}
CoreNavigator.navigate('submission', {
params,
});
CoreNavigator.navigateToSitePath(
AddonModAssignModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/submission`,
{
params,
},
);
}
/**

View File

@ -37,118 +37,115 @@
</core-navbar-buttons>
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<ion-content>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<!-- Data done in offline but not synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline || hasOfflineRatings">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}</ion-label>
</ion-item>
</ion-card>
<!-- Data done in offline but not synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline || hasOfflineRatings">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}</ion-label>
</ion-item>
</ion-card>
<ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
<ion-label id="addon-data-groupslabel">
<ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
<ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
</ion-label>
<ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel"
interface="action-sheet">
<ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
{{groupOpt.name}}
</ion-select-option>
</ion-select>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
<ion-label id="addon-data-groupslabel">
<ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
<ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
<ion-card class="core-info-card" *ngIf="!access?.timeavailable && timeAvailableFrom">
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>{{ 'addon.mod_data.notopenyet' | translate:{$a: timeAvailableFromReadable} }}</ion-label>
</ion-item>
</ion-card>
<ion-card class="core-info-card" *ngIf="!access?.timeavailable && timeAvailableTo">
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>{{ 'addon.mod_data.expired' | translate:{$a: timeAvailableToReadable} }}</ion-label>
</ion-item>
</ion-card>
<ion-card class="core-info-card" *ngIf="access && access.entrieslefttoview">>
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>
{{ 'addon.mod_data.entrieslefttoaddtoview' | translate:{$a: {entrieslefttoview: access.entrieslefttoview} } }}
</ion-label>
</ion-item>
</ion-card>
<ion-card class="core-info-card" *ngIf="access && access.entrieslefttoadd">>
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>
{{ 'addon.mod_data.entrieslefttoadd' | translate:{$a: {entriesleft: access.entrieslefttoadd} } }}
</ion-label>
</ion-item>
</ion-card>
<!-- Reset search. -->
<ng-container *ngIf="search.searching && !isEmpty">
<ion-item *ngIf="!foundRecordsTranslationData">
<ion-label>
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
</ion-label>
<ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel"
interface="action-sheet">
<ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
{{groupOpt.name}}
</ion-select-option>
</ion-select>
</ion-item>
<ion-card class="core-info-card" *ngIf="!access?.timeavailable && timeAvailableFrom">
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>{{ 'addon.mod_data.notopenyet' | translate:{$a: timeAvailableFromReadable} }}</ion-label>
</ion-item>
<ion-card class="core-success-card" *ngIf="foundRecordsTranslationData" (click)="searchReset()">
<ion-item><ion-label>
<p [innerHTML]="'addon.mod_data.foundrecords' | translate:{$a: foundRecordsTranslationData}"></p>
</ion-label></ion-item>
</ion-card>
</ng-container>
<ion-card class="core-info-card" *ngIf="!access?.timeavailable && timeAvailableTo">
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>{{ 'addon.mod_data.expired' | translate:{$a: timeAvailableToReadable} }}</ion-label>
</ion-item>
</ion-card>
<div class="addon-data-contents addon-data-entries-{{database.id}} ion-padding-horizontal" *ngIf="!isEmpty && database">
<core-style [css]="database.csstemplate" prefix=".addon-data-entries-{{database.id}}"></core-style>
<ion-card class="core-info-card" *ngIf="access && access.entrieslefttoview">>
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>
{{ 'addon.mod_data.entrieslefttoaddtoview' | translate:{$a: {entrieslefttoview: access.entrieslefttoview} } }}
</ion-label>
</ion-item>
</ion-card>
<core-compile-html [text]="entriesRendered" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
</div>
<ion-card class="core-info-card" *ngIf="access && access.entrieslefttoadd">>
<ion-item>
<ion-icon name="fas-info-circle" slot="start"></ion-icon>
<ion-label>
{{ 'addon.mod_data.entrieslefttoadd' | translate:{$a: {entriesleft: access.entrieslefttoadd} } }}
</ion-label>
</ion-item>
</ion-card>
<ion-grid *ngIf="search.page > 0 || hasNextPage">
<ion-row class="ion-align-items-center">
<ion-col *ngIf="search.page > 0">
<ion-button expand="block" fill="outline" (click)="searchEntries(search.page - 1)">
<ion-icon name="fas-chevron-left" slot="start"></ion-icon>
{{ 'core.previous' | translate }}
</ion-button>
</ion-col>
<ion-col *ngIf="hasNextPage">
<ion-button expand="block" (click)="searchEntries(search.page + 1)">
{{ 'core.next' | translate }}
<ion-icon name="fas-chevron-right" slot="end"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<!-- Reset search. -->
<ng-container *ngIf="search.searching && !isEmpty">
<ion-item *ngIf="!foundRecordsTranslationData">
<ion-label>
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
</ion-label>
</ion-item>
<core-empty-box *ngIf="isEmpty && !search.searching" icon="fas-database" [message]="'addon.mod_data.norecords' | translate">
</core-empty-box>
<ion-card class="core-success-card" *ngIf="foundRecordsTranslationData" (click)="searchReset()">
<ion-item><ion-label>
<p [innerHTML]="'addon.mod_data.foundrecords' | translate:{$a: foundRecordsTranslationData}"></p>
</ion-label></ion-item>
</ion-card>
</ng-container>
<core-empty-box *ngIf="isEmpty && search.searching" icon="fas-database" [message]="'addon.mod_data.nomatch' | translate"
class="core-empty-box-clickable">
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
</core-empty-box>
<div class="addon-data-contents addon-data-entries-{{database.id}} ion-padding-horizontal" *ngIf="!isEmpty && database">
<core-style [css]="database.csstemplate" prefix=".addon-data-entries-{{database.id}}"></core-style>
</core-loading>
<core-compile-html [text]="entriesRendered" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
</div>
<ion-grid *ngIf="search.page > 0 || hasNextPage">
<ion-row class="ion-align-items-center">
<ion-col *ngIf="search.page > 0">
<ion-button expand="block" fill="outline" (click)="searchEntries(search.page - 1)">
<ion-icon name="fas-chevron-left" slot="start"></ion-icon>
{{ 'core.previous' | translate }}
</ion-button>
</ion-col>
<ion-col *ngIf="hasNextPage">
<ion-button expand="block" (click)="searchEntries(search.page + 1)">
{{ 'core.next' | translate }}
<ion-icon name="fas-chevron-right" slot="end"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<core-empty-box *ngIf="isEmpty && !search.searching" icon="fas-database" [message]="'addon.mod_data.norecords' | translate">
</core-empty-box>
<core-empty-box *ngIf="isEmpty && search.searching" icon="fas-database" [message]="'addon.mod_data.nomatch' | translate"
class="core-empty-box-clickable">
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
</core-empty-box>
</core-loading>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd">
<ion-fab-button (click)="gotoAddEntries()" [attr.aria-label]="'addon.mod_data.addentries' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd">
<ion-fab-button (click)="gotoAddEntries()" [attr.aria-label]="'addon.mod_data.addentries' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>

View File

@ -44,6 +44,7 @@ import {
} from '../../services/data';
import { AddonModDataHelper } from '../../services/data-helper';
import { AddonModDataAutoSyncData, AddonModDataSyncProvider, AddonModDataSyncResult } from '../../services/data-sync';
import { AddonModDataModuleHandlerService } from '../../services/handlers/module';
import { AddonModDataPrefetchHandler } from '../../services/handlers/prefetch';
import { AddonModDataComponentsCompileModule } from '../components-compile.module';
import { AddonModDataSearchComponent } from '../search/search';
@ -469,7 +470,10 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
group: this.selectedGroup,
};
CoreNavigator.navigate('edit', { params });
CoreNavigator.navigateToSitePath(
`${AddonModDataModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/edit`,
{ params },
);
}
/**
@ -490,7 +494,10 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
params.offset = this.search.page * AddonModDataProvider.PER_PAGE + pageXOffset;
}
CoreNavigator.navigate(String(entryId), { params });
CoreNavigator.navigateToSitePath(
`${AddonModDataModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/${entryId}`,
{ params },
);
}
/**

View File

@ -38,6 +38,7 @@ import { AddonModDataShowLinkHandler } from './services/handlers/show-link';
import { AddonModDataSyncCronHandler } from './services/handlers/sync-cron';
import { AddonModDataTagAreaHandler } from './services/handlers/tag-area';
import { AddonModDataFieldModule } from './fields/field.module';
import { AddonModDataComponentsModule } from './components/components.module';
// List of providers (without handlers).
export const ADDON_MOD_DATA_SERVICES: Type<unknown>[] = [
@ -59,6 +60,7 @@ const routes: Routes = [
imports: [
CoreMainMenuTabRoutingModule.forChild(routes),
AddonModDataFieldModule,
AddonModDataComponentsModule,
],
providers: [
{

View File

@ -31,7 +31,7 @@
<ion-list *ngIf="subfolder && (subfolder!.files.length + subfolder!.folders.length > 0)">
<ng-container *ngFor="let folder of subfolder!.folders">
<ion-item class="item-file" (click)="openFolder(folder)" detail="true">
<ion-item class="item-file" (click)="openFolder(folder)" detail="true" button>
<ion-icon name="fas-folder" slot="start"></ion-icon>
<ion-label>
<h2>{{folder.filename}}</h2>

View File

@ -23,6 +23,7 @@ import { CoreNavigator } from '@services/navigator';
import { Md5 } from 'ts-md5';
import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder';
import { AddonModFolderFolderFormattedData, AddonModFolderHelper } from '../../services/folder-helper';
import { AddonModFolderModuleHandlerService } from '../../services/handlers/module';
/**
* Component that displays a folder.
@ -131,7 +132,10 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
const hash = <string> Md5.hashAsciiStr(folder.filepath);
CoreNavigator.navigate('../' + hash, { params });
CoreNavigator.navigateToSitePath(
`${AddonModFolderModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/${hash}`,
{ params },
);
}
}

View File

@ -42,6 +42,7 @@ import { AddonModForumProvider } from './services/forum';
import { AddonModForumOfflineProvider } from './services/forum-offline';
import { AddonModForumHelperProvider } from './services/forum-helper';
import { AddonModForumSyncProvider } from './services/forum-sync';
import { COURSE_CONTENTS_PATH } from '@features/course/course.module';
export const ADDON_MOD_FORUM_SERVICES: Type<unknown>[] = [
AddonModForumProvider,
@ -62,12 +63,12 @@ const mainMenuRoutes: Routes = [
...conditionalRoutes(
[
{
path: `course/index/contents/${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule),
},
{
path: `course/index/contents/${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule),
},
],

View File

@ -44,6 +44,7 @@ import {
AddonModGlossarySyncProvider,
AddonModGlossarySyncResult,
} from '../../services/glossary-sync';
import { AddonModGlossaryModuleHandlerService } from '../../services/handlers/module';
import { AddonModGlossaryPrefetchHandler } from '../../services/handlers/prefetch';
import { AddonModGlossaryModePickerPopoverComponent } from '../mode-picker/mode-picker';
@ -93,6 +94,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
this.entries = new AddonModGlossaryEntriesManager(
route.component,
this,
courseContentsPage ? `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/` : '',
);
}
@ -524,8 +527,18 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem>
onlineEntries: AddonModGlossaryEntry[] = [];
offlineEntries: AddonModGlossaryOfflineEntry[] = [];
constructor(pageComponent: unknown) {
protected glossaryPathPrefix: string;
protected component: AddonModGlossaryIndexComponent;
constructor(
pageComponent: unknown,
component: AddonModGlossaryIndexComponent,
glossaryPathPrefix: string,
) {
super(pageComponent);
this.component = component;
this.glossaryPathPrefix = glossaryPathPrefix;
}
/**
@ -609,27 +622,30 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem>
*/
protected getItemPath(entry: EntryItem): string {
if (this.isOnlineEntry(entry)) {
return `entry/${entry.id}`;
return `${this.glossaryPathPrefix}entry/${entry.id}`;
}
if (this.isOfflineEntry(entry)) {
return `edit/${entry.timecreated}`;
return `${this.glossaryPathPrefix}edit/${entry.timecreated}`;
}
return 'edit/0';
return `${this.glossaryPathPrefix}edit/0`;
}
/**
* @inheritdoc
*/
getItemQueryParams(entry: EntryItem): Params {
const params: Params = {
cmId: this.component.module.id,
courseId: this.component.courseId,
};
if (this.isOfflineEntry(entry)) {
return {
concept: entry.concept,
};
params.concept = entry.concept;
}
return {};
return params;
}
/**

View File

@ -12,14 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { conditionalRoutes } from '@/app/app-routing.module';
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
import { Routes } from '@angular/router';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { COURSE_CONTENTS_PATH } from '@features/course/course.module';
import { CoreCourseContentsRoutingModule } from '@features/course/pages/contents/contents-routing.module';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
import { CoreCronDelegate } from '@services/cron';
import { CoreScreen } from '@services/screen';
import { CORE_SITE_SCHEMAS } from '@services/sites';
import { AddonModGlossaryComponentsModule } from './components/components.module';
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/glossary';
@ -44,23 +48,43 @@ export const ADDON_MOD_GLOSSARY_SERVICES: Type<unknown>[] = [
];
const mainMenuRoutes: Routes = [
{
path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entryId`,
loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule),
},
{
path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/edit/:timecreated`,
loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule),
},
{
path: AddonModGlossaryModuleHandlerService.PAGE_NAME,
loadChildren: () => import('./glossary-lazy.module').then(m => m.AddonModGlossaryLazyModule),
},
...conditionalRoutes(
[
{
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entryId`,
loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule),
},
{
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/edit/:timecreated`,
loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule),
},
],
() => CoreScreen.isMobile,
),
];
const courseContentsRoutes: Routes = conditionalRoutes(
[
{
path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entryId`,
loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule),
},
{
path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/edit/:timecreated`,
loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule),
},
],
() => CoreScreen.isTablet,
);
@NgModule({
imports: [
CoreMainMenuTabRoutingModule.forChild(mainMenuRoutes),
CoreCourseContentsRoutingModule.forChild({ children: courseContentsRoutes }),
AddonModGlossaryComponentsModule,
],
providers: [

View File

@ -44,6 +44,7 @@ import {
AddonModH5PActivitySyncResult,
} from '../../services/h5pactivity-sync';
import { CoreFileHelper } from '@services/file-helper';
import { AddonModH5PActivityModuleHandlerService } from '../../services/handlers/module';
/**
* Component that displays an H5P activity entry page.
@ -375,7 +376,9 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
const userId = CoreSites.getCurrentSiteUserId();
try {
await CoreNavigator.navigate(`userattempts/${userId}`);
await CoreNavigator.navigateToSitePath(
`${AddonModH5PActivityModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/userattempts/${userId}`,
);
} finally {
this.isOpeningPage = false;
}

View File

@ -47,6 +47,7 @@ import {
AddonModLessonSyncProvider,
AddonModLessonSyncResult,
} from '../../services/lesson-sync';
import { AddonModLessonModuleHandlerService } from '../../services/handlers/module';
/**
* Component that displays a lesson entry page.
@ -424,12 +425,15 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
pageId = continueLast ? this.accessInfo.lastpageseen : this.accessInfo.firstpageid;
}
await CoreNavigator.navigate('player', {
params: {
pageId: pageId,
password: this.password,
await CoreNavigator.navigateToSitePath(
`${AddonModLessonModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/player`,
{
params: {
pageId: pageId,
password: this.password,
},
},
});
);
// Detect if anything was sent to server.
this.hasPlayed = true;
@ -472,14 +476,17 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
return;
}
CoreNavigator.navigate('player', {
params: {
pageId: this.retakeToReview.pageid,
password: this.password,
review: true,
retake: this.retakeToReview.retake,
CoreNavigator.navigateToSitePath(
`${AddonModLessonModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/player`,
{
params: {
pageId: this.retakeToReview.pageid,
password: this.password,
review: true,
retake: this.retakeToReview.retake,
},
},
});
);
}
/**
@ -695,11 +702,9 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* @return Promise resolved when done.
*/
async openRetake(userId: number): Promise<void> {
await CoreNavigator.navigate('user-retake', {
params: {
userId,
},
});
CoreNavigator.navigateToSitePath(
`${AddonModLessonModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/user-retake/${userId}`,
);
}
/**

View File

@ -30,7 +30,7 @@ const routes: Routes = [
loadChildren: () => import('./pages/player/player.module').then( m => m.AddonModLessonPlayerPageModule),
},
{
path: ':courseId/:cmId/user-retake',
path: ':courseId/:cmId/user-retake/:userId',
loadChildren: () => import('./pages/user-retake/user-retake.module').then( m => m.AddonModLessonUserRetakePageModule),
},
];

View File

@ -52,6 +52,7 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul
siteId: string,
): Promise<void> {
const moduleId = Number(params.id);
const userId = Number(params.userid) || 0;
const modal = await CoreDomUtils.showModalLoading();
@ -65,9 +66,8 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul
if (accessInfo.canviewreports) {
// User can view reports, go to view the report.
CoreNavigator.navigateToSitePath(
AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake`,
AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake/${userId}`,
{
params: { userId: Number(params.userid) },
siteId,
},
);

View File

@ -102,14 +102,17 @@ export class AddonModLessonReportLinkHandlerService extends CoreContentLinksHand
// Get the module object.
const module = await CoreCourse.getModuleBasicInfo(moduleId, siteId);
courseId = courseId || module.course;
const params = {
module: module,
courseId: courseId || module.course,
action: 'report',
group: groupId === undefined || isNaN(groupId) ? null : groupId,
};
CoreNavigator.navigateToSitePath(AddonModLessonModuleHandlerService.PAGE_NAME, { params, siteId });
CoreNavigator.navigateToSitePath(
`${AddonModLessonModuleHandlerService.PAGE_NAME}/${courseId}/${module.id}`,
{ params, siteId },
);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error processing link.');
} finally {
@ -143,12 +146,11 @@ export class AddonModLessonReportLinkHandlerService extends CoreContentLinksHand
courseId = courseId || module.course;
const params = {
userId: userId,
retake: retake || 0,
};
CoreNavigator.navigateToSitePath(
AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake`,
AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake/${userId}`,
{ params, siteId },
);
} catch (error) {

View File

@ -27,6 +27,7 @@ import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { AddonModQuizModuleHandlerService } from '../../services/handlers/module';
import { AddonModQuizPrefetchHandler } from '../../services/handlers/prefetch';
import {
AddonModQuiz,
@ -413,7 +414,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
try {
await AddonModQuiz.getAttemptReview(attemptId, { page: -1, cmId: this.module.id });
await CoreNavigator.navigate(`review/${attemptId}`);
await CoreNavigator.navigateToSitePath(
`${AddonModQuizModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/review/${attemptId}`,
);
} catch {
// Ignore errors.
}
@ -534,11 +537,14 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
protected openQuiz(): void {
this.hasPlayed = true;
CoreNavigator.navigate('player', {
params: {
moduleUrl: this.module.url,
CoreNavigator.navigateToSitePath(
`${AddonModQuizModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/player`,
{
params: {
moduleUrl: this.module.url,
},
},
});
);
}
/**
@ -659,7 +665,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
* @return Promise resolved when done.
*/
async viewAttempt(attemptId: number): Promise<void> {
CoreNavigator.navigate(`attempt/${attemptId}`);
await CoreNavigator.navigateToSitePath(
`${AddonModQuizModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/attempt/${attemptId}`,
);
}
/**

View File

@ -24,6 +24,7 @@ import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, ModalController, Translate } from '@singletons';
import { AddonModQuizPreflightModalComponent } from '../components/preflight-modal/preflight-modal';
import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate';
import { AddonModQuizModuleHandlerService } from './handlers/module';
import {
AddonModQuiz,
AddonModQuizAttemptWSData,
@ -238,12 +239,15 @@ export class AddonModQuizHelperProvider {
courseId = courseId || module.course;
// Go to the review page.
await CoreNavigator.navigateToSitePath(`mod_quiz/${courseId}/${module.id}/review/${attemptId}`, {
params: {
page: page == undefined || isNaN(page) ? -1 : page,
await CoreNavigator.navigateToSitePath(
`${AddonModQuizModuleHandlerService.PAGE_NAME}/${courseId}/${module.id}/review/${attemptId}`,
{
params: {
page: page == undefined || isNaN(page) ? -1 : page,
},
siteId,
},
siteId,
});
);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'An error occurred while loading the required data.');
} finally {

View File

@ -23,6 +23,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { AddonModScormModuleHandlerService } from '../../services/handlers/module';
import { AddonModScormPrefetchHandler } from '../../services/handlers/prefetch';
import {
AddonModScorm,
@ -544,15 +545,18 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
}
}, this.siteId);
CoreNavigator.navigate('player', {
params: {
mode: preview ? AddonModScormProvider.MODEBROWSE : AddonModScormProvider.MODENORMAL,
moduleUrl: this.module.url,
newAttempt: !!this.startNewAttempt,
organizationId: this.currentOrganization.identifier,
scoId: scoId,
CoreNavigator.navigateToSitePath(
`${AddonModScormModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/player`,
{
params: {
mode: preview ? AddonModScormProvider.MODEBROWSE : AddonModScormProvider.MODENORMAL,
moduleUrl: this.module.url,
newAttempt: !!this.startNewAttempt,
organizationId: this.currentOrganization.identifier,
scoId: scoId,
},
},
});
);
}
/**

View File

@ -46,52 +46,50 @@
</core-navbar-buttons>
<!-- Content. -->
<ion-content>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<div *ngIf="description || pageIsOffline || hasOffline || pageWarning">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
<core-loading [hideUntil]="loaded" class="core-loading-center">
<div *ngIf="description || pageIsOffline || hasOffline || pageWarning">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<!-- Wiki has something offline. -->
<ion-card class="core-warning-card" *ngIf="pageIsOffline || hasOffline">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>
<span *ngIf="pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: pageStr} }}</span>
<span *ngIf="!pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</span>
</ion-label>
</ion-item>
</ion-card>
<!-- Page warning. -->
<ion-card class="core-warning-card" *ngIf="pageWarning">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ pageWarning }}</ion-label>
</ion-item>
</ion-card>
</div>
<div class="ion-padding addon-mod_wiki-page-content">
<article [ngClass]="{'addon-mod_wiki-noedit': !canEdit}">
<core-format-text *ngIf="pageContent" [component]="component" [componentId]="componentId" [text]="pageContent"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
</core-format-text>
<core-empty-box *ngIf="!pageContent" icon="fas-file-alt" [message]="'addon.mod_wiki.nocontent' | translate"
[inline]="true">
</core-empty-box>
</article>
<!-- Wiki has something offline. -->
<ion-card class="core-warning-card" *ngIf="pageIsOffline || hasOffline">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>
<span *ngIf="pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: pageStr} }}</span>
<span *ngIf="!pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</span>
</ion-label>
</ion-item>
</ion-card>
<!-- Page warning. -->
<ion-card class="core-warning-card" *ngIf="pageWarning">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ pageWarning }}</ion-label>
</ion-item>
</ion-card>
<div class="ion-margin-top" *ngIf="tagsEnabled && tags.length > 0">
<b>{{ 'core.tag.tags' | translate }}:</b>
<core-tag-list [tags]="tags"></core-tag-list>
</div>
<div class="ion-padding addon-mod_wiki-page-content">
<article [ngClass]="{'addon-mod_wiki-noedit': !canEdit}">
<core-format-text *ngIf="pageContent" [component]="component" [componentId]="componentId" [text]="pageContent"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
<core-empty-box *ngIf="!pageContent" icon="fas-file-alt" [message]="'addon.mod_wiki.nocontent' | translate"
[inline]="true">
</core-empty-box>
</article>
</div>
</core-loading>
<div class="ion-margin-top" *ngIf="tagsEnabled && tags.length > 0">
<b>{{ 'core.tag.tags' | translate }}:</b>
<core-tag-list [tags]="tags"></core-tag-list>
</div>
</div>
</core-loading>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canEdit">
<ion-fab-button (click)="goToNewPage()" [attr.aria-label]="'addon.mod_wiki.createpage' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canEdit">
<ion-fab-button (click)="goToNewPage()" [attr.aria-label]="'addon.mod_wiki.createpage' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>

View File

@ -31,6 +31,7 @@ import { ModalController, PopoverController, Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { Md5 } from 'ts-md5';
import { AddonModWikiPageDBRecord } from '../../services/database/wiki';
import { AddonModWikiModuleHandlerService } from '../../services/handlers/module';
import {
AddonModWiki,
AddonModWikiPageContents,
@ -443,14 +444,17 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
* Open the view to create the first page of the wiki.
*/
protected goToCreateFirstPage(): void {
CoreNavigator.navigate('../../edit', {
params: {
pageTitle: this.wiki!.firstpagetitle,
wikiId: this.currentSubwiki?.wikiid,
userId: this.currentSubwiki?.userid,
groupId: this.currentSubwiki?.groupid,
CoreNavigator.navigateToSitePath(
`${AddonModWikiModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/edit`,
{
params: {
pageTitle: this.wiki!.firstpagetitle,
wikiId: this.currentSubwiki?.wikiid,
userId: this.currentSubwiki?.userid,
groupId: this.currentSubwiki?.groupid,
},
},
});
);
}
/**
@ -478,7 +482,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
pageParams.groupId = this.currentSubwiki.groupid;
}
CoreNavigator.navigate('../../edit', { params: pageParams });
CoreNavigator.navigateToSitePath(
`${AddonModWikiModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/edit`,
{ params: pageParams },
);
} else if (this.currentSubwiki) {
// No page loaded, the wiki doesn't have first page.
this.goToCreateFirstPage();
@ -505,7 +512,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
pageParams.groupId = this.currentSubwiki.groupid;
}
CoreNavigator.navigate('../../edit', { params: pageParams });
CoreNavigator.navigateToSitePath(
`${AddonModWikiModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/edit`,
{ params: pageParams },
);
} else if (this.currentSubwiki) {
// No page loaded, the wiki doesn't have first page.
this.goToCreateFirstPage();
@ -550,12 +560,15 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
timestamp: Date.now(),
}));
await CoreNavigator.navigate(`../${hash}`, {
params: {
module: this.module,
...options,
CoreNavigator.navigateToSitePath(
`${AddonModWikiModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/page/${hash}`,
{
params: {
module: this.module,
...options,
},
},
});
);
}
/**

View File

@ -33,7 +33,7 @@
</core-rich-text-editor>
</ion-item>
<ion-item *ngIf="!wrongVersionLock" class="ion-text-center addon-mod_wiki-wrongversionlock" >
<ion-item *ngIf="wrongVersionLock" class="ion-text-center addon-mod_wiki-wrongversionlock" >
<ion-label>
<ion-badge color="danger" class="ion-padding">{{ 'addon.mod_wiki.wrongversionlock' | translate }}</ion-badge>
</ion-label>

View File

@ -29,7 +29,7 @@ export class CoreFabDirective implements OnDestroy {
protected static readonly PADDINGBOTTOM = 56;
protected element?: HTMLElement;
protected scrollElement?: HTMLElement;
protected done = false;
constructor(protected content: IonContent) {
@ -41,10 +41,10 @@ export class CoreFabDirective implements OnDestroy {
*/
async asyncInit(): Promise<void> {
if (this.content) {
this.element = await this.content.getScrollElement();
this.scrollElement = await this.content.getScrollElement();
if (!this.done) {
const bottom = parseInt(this.element.style.paddingBottom, 10) || 0;
this.element.style.paddingBottom = (bottom + CoreFabDirective.PADDINGBOTTOM) + 'px';
const bottom = parseInt(this.scrollElement.style.paddingBottom, 10) || 0;
this.scrollElement.style.paddingBottom = (bottom + CoreFabDirective.PADDINGBOTTOM) + 'px';
this.done = true;
}
}
@ -54,9 +54,9 @@ export class CoreFabDirective implements OnDestroy {
* Destroy component.
*/
ngOnDestroy(): void {
if (this.done && this.element) {
const bottom = parseInt(this.element.style.paddingBottom, 10) || 0;
this.element.style.paddingBottom = (bottom + CoreFabDirective.PADDINGBOTTOM) + 'px';
if (this.done && this.scrollElement) {
const bottom = parseInt(this.scrollElement.style.paddingBottom, 10) || 0;
this.scrollElement.style.paddingBottom = (bottom - CoreFabDirective.PADDINGBOTTOM) + 'px';
}
}

View File

@ -126,7 +126,6 @@ import { ADDON_MOD_ASSIGN_SERVICES } from '@addons/mod/assign/assign.module';
import { ADDON_MOD_BOOK_SERVICES } from '@addons/mod/book/book.module';
import { ADDON_MOD_CHAT_SERVICES } from '@addons/mod/chat/chat.module';
import { ADDON_MOD_CHOICE_SERVICES } from '@addons/mod/choice/choice.module';
import { ADDON_MOD_DATA_SERVICES } from '@addons/mod/data/data.module';
import { ADDON_MOD_FEEDBACK_SERVICES } from '@addons/mod/feedback/feedback.module';
import { ADDON_MOD_FOLDER_SERVICES } from '@addons/mod/folder/folder.module';
import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module';
@ -292,7 +291,6 @@ export class CoreCompileProvider {
...ADDON_MOD_BOOK_SERVICES,
...ADDON_MOD_CHAT_SERVICES,
...ADDON_MOD_CHOICE_SERVICES,
...ADDON_MOD_DATA_SERVICES,
...ADDON_MOD_FEEDBACK_SERVICES,
...ADDON_MOD_FOLDER_SERVICES,
...ADDON_MOD_FORUM_SERVICES,

View File

@ -15,14 +15,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
export const COURSE_INDEX_PATH = ':courseId';
const routes: Routes = [
{
path: '',
redirectTo: 'index',
pathMatch: 'full',
},
{
path: ':courseId',
path: COURSE_INDEX_PATH,
loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule),
},
{

View File

@ -39,6 +39,7 @@ import { CoreCourseModuleDelegateService } from './services/module-delegate';
import { CoreCourseOptionsDelegateService } from './services/course-options-delegate';
import { CoreCourseOfflineProvider } from './services/course-offline';
import { CoreCourseSyncProvider } from './services/sync';
import { COURSE_INDEX_PATH } from '@features/course/course-lazy.module';
export const CORE_COURSE_SERVICES: Type<unknown>[] = [
CoreCourseProvider,
@ -52,16 +53,20 @@ export const CORE_COURSE_SERVICES: Type<unknown>[] = [
CoreCourseSyncProvider,
];
export const COURSE_PAGE_NAME = 'course';
export const CONTENTS_PAGE_NAME = 'contents';
export const COURSE_CONTENTS_PATH = `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${CONTENTS_PAGE_NAME}`;
const routes: Routes = [
{
path: 'course',
path: COURSE_PAGE_NAME,
loadChildren: () => import('@features/course/course-lazy.module').then(m => m.CoreCourseLazyModule),
},
];
const courseIndexRoutes: Routes = [
{
path: 'contents',
path: CONTENTS_PAGE_NAME,
loadChildren: () => import('./pages/contents/contents.module').then(m => m.CoreCourseContentsPageModule),
},
];

View File

@ -25,6 +25,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreUtils } from '@services/utils/utils';
import { CoreTextUtils } from '@services/utils/text';
import { CoreNavigator } from '@services/navigator';
import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
/**
* Page that displays the list of courses the user is enrolled in.
@ -46,7 +47,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
protected selectTabObserver: CoreEventObserver;
protected firstTabName?: string;
protected contentsTab: CoreTabsOutletTab = {
page: 'contents',
page: CONTENTS_PAGE_NAME,
title: 'core.course.contents',
pageParams: {},
};

View File

@ -1848,7 +1848,7 @@ export class CoreCourseHelperProvider {
params = params || {};
Object.assign(params, { course: course });
await CoreNavigator.navigateToSitePath('course/' + course.id, { siteId, params });
await CoreNavigator.navigateToSitePath(`course/${course.id}`, { siteId, params });
}
}

View File

@ -177,7 +177,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
Object.assign(params, { course: course });
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
CoreNavigator.navigateToSitePath('course/' + course.id, { params });
CoreNavigator.navigateToSitePath(`course/${course.id}`, { params });
}
/**

View File

@ -496,3 +496,11 @@ ion-button.core-button-select {
textarea:not([core-auto-rows]) {
height: 200px;
}
ion-fab[core-fab] {
position: fixed;
}
ion-content > ion-fab[core-fab] {
position: absolute;
}