MOBILE-3631 core: Add missing split views

main
Pau Ferrer Ocaña 2021-01-28 17:23:57 +01:00
parent 2da305bee8
commit ec97894f74
37 changed files with 538 additions and 351 deletions

View File

@ -13,7 +13,13 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Route, RouterModule, Routes } from '@angular/router';
export const AddonBadgesIssueRoute: Route = {
path: 'issue',
loadChildren: () => import('./pages/issued-badge/issued-badge.module').then( m => m.AddonBadgesIssuedBadgePageModule),
};
const routes: Routes = [
{
@ -21,10 +27,7 @@ const routes: Routes = [
redirectTo: 'user',
pathMatch: 'full',
},
{
path: 'issue',
loadChildren: () => import('./pages/issued-badge/issued-badge.module').then( m => m.AddonBadgesIssuedBadgePageModule),
},
AddonBadgesIssueRoute,
{
path: 'user',
loadChildren: () => import('./pages/user-badges/user-badges.module').then( m => m.AddonBadgesUserBadgesPageModule),

View File

@ -22,6 +22,7 @@ import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges';
import { CoreUtils } from '@services/utils/utils';
import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
import { CoreNavigator } from '@services/navigator';
import { ActivatedRoute } from '@angular/router';
/**
* Page that displays the list of calendar events.
@ -42,16 +43,24 @@ export class AddonBadgesIssuedBadgePage implements OnInit {
badgeLoaded = false;
currentTime = 0;
constructor(
protected route: ActivatedRoute,
) { }
/**
* View loaded.
*/
ngOnInit(): void {
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId') || this.courseId; // Use 0 for site badges.
this.userId = CoreNavigator.instance.getRouteNumberParam('userId') || CoreSites.instance.getCurrentSite()!.getUserId();
this.badgeHash = CoreNavigator.instance.getRouteParam('badgeHash') || '';
this.route.queryParams.subscribe(() => {
this.badgeLoaded = false;
this.fetchIssuedBadge().finally(() => {
this.badgeLoaded = true;
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId') || this.courseId; // Use 0 for site badges.
this.userId = CoreNavigator.instance.getRouteNumberParam('userId') || CoreSites.instance.getCurrentSite()!.getUserId();
this.badgeHash = CoreNavigator.instance.getRouteParam('badgeHash') || '';
this.fetchIssuedBadge().finally(() => {
this.badgeLoaded = true;
});
});
}

View File

@ -6,29 +6,31 @@
<ion-title>{{ 'addon.badges.badges' | translate }}</ion-title>
</ion-toolbar>
</ion-header>
<!-- @todo <core-split-view>-->
<ion-content>
<ion-refresher slot="fixed" [disabled]="!badgesLoaded" (ionRefresh)="refreshBadges($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="badgesLoaded">
<core-empty-box *ngIf="!badges || badges.length == 0" icon="fas-trophy" [message]="'addon.badges.nobadges' | translate">
</core-empty-box>
<core-split-view>
<ion-refresher slot="fixed" [disabled]="!badgesLoaded" (ionRefresh)="refreshBadges($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="badgesLoaded">
<core-empty-box *ngIf="!badges || badges.length == 0" icon="fas-trophy"
[message]="'addon.badges.nobadges' | translate">
</core-empty-box>
<ion-list *ngIf="badges && badges.length" class="ion-no-margin">
<ion-item class="ion-text-wrap" *ngFor="let badge of badges" [title]="badge.name"
(click)="loadIssuedBadge(badge.uniquehash)" [class.core-selected-item]="badge.uniquehash == badgeHash">
<ion-avatar slot="start">
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
</ion-avatar>
<ion-label>
<h2>{{ badge.name }}</h2>
<p>{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
</ion-label>
<ion-badge slot="end" color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
{{ 'addon.badges.expired' | translate }}
</ion-badge>
</ion-item>
</ion-list>
</core-loading>
<ion-list *ngIf="badges && badges.length" class="ion-no-margin">
<ion-item class="ion-text-wrap" *ngFor="let badge of badges" [title]="badge.name"
(click)="loadIssuedBadge(badge.uniquehash)" [class.core-selected-item]="badge.uniquehash == badgeHash">
<ion-avatar slot="start">
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
</ion-avatar>
<ion-label>
<h2>{{ badge.name }}</h2>
<p>{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
</ion-label>
<ion-badge slot="end" color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
{{ 'addon.badges.expired' | translate }}
</ion-badge>
</ion-item>
</ion-list>
</core-loading>
</core-split-view>
</ion-content>

View File

@ -17,15 +17,34 @@ import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { conditionalRoutes } from '@/app/app-routing.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonBadgesUserBadgesPage } from './user-badges.page';
import { AddonBadgesIssueRoute } from '@addons/badges/badges-lazy.module';
const routes: Routes = [
const mobileRoutes: Routes = [
{
path: '',
component: AddonBadgesUserBadgesPage,
},
AddonBadgesIssueRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonBadgesUserBadgesPage,
children: [
AddonBadgesIssueRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -20,7 +20,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreScreen } from '@services/screen';
/**
* Page that displays the list of calendar events.
@ -31,8 +31,6 @@ import { CoreNavigator } from '@services/navigator';
})
export class AddonBadgesUserBadgesPage implements OnInit {
// @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
courseId = 0;
userId!: number;
@ -50,11 +48,10 @@ export class AddonBadgesUserBadgesPage implements OnInit {
this.userId = CoreNavigator.instance.getRouteNumberParam('userId') || CoreSites.instance.getCurrentSite()!.getUserId();
this.fetchBadges().finally(() => {
// @todo splitview
/* if (!this.badgeHash && this.splitviewCtrl.isOn() && this.badges.length > 0) {
if (!this.badgeHash && CoreScreen.instance.isTablet && this.badges.length > 0) {
// Take first and load it.
this.loadIssuedBadge(this.badges[0].uniquehash);
}*/
}
this.badgesLoaded = true;
});
}
@ -99,9 +96,11 @@ export class AddonBadgesUserBadgesPage implements OnInit {
loadIssuedBadge(badgeHash: string): void {
this.badgeHash = badgeHash;
const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash };
// @todo use splitview.
// this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params);
CoreNavigator.instance.navigateToSitePath('/badges/issue', { params });
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/badges/user/issue');
const path = (splitViewLoaded ? '../' : '') + 'issue';
CoreNavigator.instance.navigate(path, { params });
}
}

View File

@ -13,10 +13,21 @@
// limitations under the License.
import { Injector, NgModule } from '@angular/core';
import { RouterModule, ROUTES, Routes } from '@angular/router';
import { Route, RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
export const AddonCalendarEditRoute: Route = {
path: 'edit',
loadChildren: () =>
import('@/addons/calendar/pages/edit-event/edit-event.module').then(m => m.AddonCalendarEditEventPageModule),
};
export const AddonCalendarEventRoute: Route ={
path: 'event',
loadChildren: () => import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
};
function buildRoutes(injector: Injector): Routes {
return [
{
@ -37,16 +48,8 @@ function buildRoutes(injector: Injector): Routes {
loadChildren: () =>
import('@/addons/calendar/pages/day/day.module').then(m => m.AddonCalendarDayPageModule),
},
{
path: 'event',
loadChildren: () =>
import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
},
{
path: 'edit',
loadChildren: () =>
import('@/addons/calendar/pages/edit-event/edit-event.module').then(m => m.AddonCalendarEditEventPageModule),
},
AddonCalendarEventRoute,
AddonCalendarEditRoute,
...buildTabMainRoutes(injector, {
redirectTo: 'index',
pathMatch: 'full',

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, Optional } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { IonRefresher } from '@ionic/angular';
import { CoreEvents } from '@singletons/events';
@ -23,7 +23,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { CoreCategoryData, CoreCourses, CoreCourseSearchedData, CoreEnrolledCourseData } from '@features/courses/services/courses';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreEditorRichTextEditorComponent } from '@features/editor/components/rich-text-editor/rich-text-editor.ts';
import {
AddonCalendarProvider,
@ -91,6 +91,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
constructor(
protected fb: FormBuilder,
@Optional() protected svComponent: CoreSplitViewComponent,
) {
this.currentSite = CoreSites.instance.getCurrentSite()!;
@ -569,14 +570,15 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
}
}
/* if (this.svComponent && this.svComponent.isOn()) {
if (this.svComponent?.isOn()) {
// Empty form.
this.hasOffline = false;
this.form.reset(this.originalData);
this.originalData = CoreUtils.instance.clone(this.form.value);
} else {*/
this.originalData = undefined; // Avoid asking for confirmation.
CoreNavigator.instance.back();
} else {
this.originalData = undefined; // Avoid asking for confirmation.
CoreNavigator.instance.back();
}
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, Optional } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { AlertOptions } from '@ionic/core';
import {
@ -37,12 +37,14 @@ import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreCourse } from '@features/course/services/course';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreGroups } from '@services/groups';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { Network, NgZone, Translate } from '@singletons';
import { Subscription } from 'rxjs';
import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils';
import { AddonCalendarReminderDBRecord } from '../../services/database/calendar';
import { ActivatedRoute } from '@angular/router';
import { CoreScreen } from '@services/screen';
/**
* Page that displays a single calendar event.
@ -85,11 +87,15 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
syncIcon = 'spinner'; // Sync icon.
isSplitViewOn = false;
constructor() {
constructor(
@Optional() protected svComponent: CoreSplitViewComponent,
protected route: ActivatedRoute,
) {
this.notificationsEnabled = CoreLocalNotifications.instance.isAvailable();
this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
// this.isSplitViewOn = this.svComponent && this.svComponent.isOn();
this.isSplitViewOn = this.svComponent?.isOn();
// Check if site supports editing and deleting. No need to check allowed types, event.canedit already does it.
this.canEdit = AddonCalendar.instance.canEditEventsInSite();
@ -145,18 +151,22 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
* View loaded.
*/
ngOnInit(): void {
const eventId = CoreNavigator.instance.getRouteNumberParam('id');
if (!eventId) {
CoreDomUtils.instance.showErrorModal('Event ID not supplied.');
CoreNavigator.instance.back();
this.route.queryParams.subscribe(() => {
this.eventLoaded = false;
return;
}
const eventId = CoreNavigator.instance.getRouteNumberParam('id');
if (!eventId) {
CoreDomUtils.instance.showErrorModal('Event ID not supplied.');
CoreNavigator.instance.back();
this.eventId = eventId;
this.syncIcon = 'spinner';
return;
}
this.fetchEvent();
this.eventId = eventId;
this.syncIcon = 'spinner';
this.fetchEvent();
});
}
/**
@ -501,9 +511,9 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
CoreDomUtils.instance.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
// Event deleted, close the view.
/* if (!this.svComponent || !this.svComponent.isOn()) {
this.navCtrl.pop();
}*/
if (CoreScreen.instance.isMobile) {
CoreNavigator.instance.back();
}
} else {
// Event deleted in offline, just mark it as deleted.
this.event.deleted = true;
@ -558,9 +568,9 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
CoreDomUtils.instance.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
// Event was deleted, close the view.
/* if (!this.svComponent || !this.svComponent.isOn()) {
this.navCtrl.pop();
}*/
if (CoreScreen.instance.isMobile) {
CoreNavigator.instance.back();
}
} else if (data.events && (!isManual || data.source != 'event')) {
const event = data.events.find((ev) => ev.id == this.eventId);

View File

@ -89,9 +89,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
// Listen for events added. When an event is added, reload the data.
this.newEventObserver = CoreEvents.on(
this.newEventObserver = CoreEvents.on<AddonCalendarUpdatedEventEvent>(
AddonCalendarProvider.NEW_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
(data) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, false);
@ -107,9 +107,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
}, this.currentSiteId);
// Listen for events edited. When an event is edited, reload the data.
this.editEventObserver = CoreEvents.on(
this.editEventObserver = CoreEvents.on<AddonCalendarUpdatedEventEvent>(
AddonCalendarProvider.EDIT_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
(data) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, false);
@ -125,7 +125,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
}, this.currentSiteId);
// Refresh data if calendar events are synchronized manually but not by this page.
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data: AddonCalendarSyncEvents) => {
this.manualSyncObserver = CoreEvents.on<AddonCalendarSyncEvents>(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
if (data && data.source != 'index') {
this.loaded = false;
this.refreshData(false, false);
@ -143,9 +143,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.hasOffline = await AddonCalendarOffline.instance.hasOfflineData();
}, this.currentSiteId);
this.filterChangedObserver = CoreEvents.on(
this.filterChangedObserver = CoreEvents.on<AddonCalendarFilter>(
AddonCalendarProvider.FILTER_CHANGED_EVENT,
async (filterData: AddonCalendarFilter) => {
async (filterData) => {
this.filter = filterData;
// Course viewed has changed, check if the user can create events for this course calendar.

View File

@ -19,8 +19,8 @@
</ion-buttons>
</ion-toolbar>
</ion-header>
<!--<core-split-view>-->
<ion-content>
<ion-content>
<core-split-view>
<ion-refresher slot="fixed" [disabled]="!eventsLoaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
@ -86,5 +86,5 @@
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<!--</core-split-view>-->
</core-split-view>
</ion-content>

View File

@ -17,18 +17,40 @@ import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { AddonCalendarEventRoute, AddonCalendarEditRoute } from '@addons/calendar/calendar-lazy.module';
import { conditionalRoutes } from '@/app/app-routing.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonCalendarListPage } from './list.page';
const routes: Routes = [
const splitviewRoutes = [AddonCalendarEditRoute, AddonCalendarEventRoute];
const mobileRoutes: Routes = [
{
path: '',
component: AddonCalendarListPage,
},
...splitviewRoutes,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonCalendarListPage,
children: [
...splitviewRoutes,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({
imports: [
RouterModule.forChild(routes),

View File

@ -30,7 +30,7 @@ import { CoreSites } from '@services/sites';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreApp } from '@services/app';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import moment from 'moment';
import { CoreConstants } from '@/core/constants';
import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter';
@ -52,7 +52,7 @@ import { CoreNavigator } from '@services/navigator';
export class AddonCalendarListPage implements OnInit, OnDestroy {
@ViewChild(IonContent) content?: IonContent;
// @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
@ViewChild(CoreSplitViewComponent) splitviewCtrl?: CoreSplitViewComponent;
protected initialTime = 0;
protected daysLoaded = 0;
@ -117,30 +117,28 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
}
// Listen for events added. When an event is added, reload the data.
this.newEventObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data: AddonCalendarUpdatedEventEvent) => {
this.newEventObserver = CoreEvents.on<AddonCalendarUpdatedEventEvent>(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
if (data && data.eventId) {
/* if (this.splitviewCtrl.isOn()) {
// Discussion added, clear details page.
this.splitviewCtrl.emptyDetails();
}*/
this.eventsLoaded = false;
this.refreshEvents(true, false).finally(() => {
// In tablet mode try to open the event (only if it's an online event).
/* if (this.splitviewCtrl.isOn() && data.event.id > 0) {
this.gotoEvent(data.event.id);
}*/
if (this.splitviewCtrl?.isOn() && data.eventId > 0) {
this.gotoEvent(data.eventId);
} else if (this.splitviewCtrl?.isOn()) {
// Discussion added, clear details page.
this.emptySplitView();
}
});
}
}, this.currentSiteId);
// Listen for new event discarded event. When it does, reload the data.
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
/* if (this.splitviewCtrl.isOn()) {
if (this.splitviewCtrl?.isOn()) {
// Discussion added, clear details page.
this.splitviewCtrl.emptyDetails();
}*/
this.emptySplitView();
}
this.eventsLoaded = false;
this.refreshEvents(true, false);
@ -155,14 +153,14 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
}, this.currentSiteId);
// Refresh data if calendar events are synchronized automatically.
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
this.syncObserver = CoreEvents.on<AddonCalendarSyncEvents>(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
this.eventsLoaded = false;
this.refreshEvents();
/* if (this.splitviewCtrl.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
if (this.splitviewCtrl?.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
// Current selected event was deleted. Clear details.
this.splitviewCtrl.emptyDetails();
} */
this.emptySplitView();
}
}, this.currentSiteId);
// Refresh data if calendar events are synchronized manually but not by this page.
@ -172,10 +170,10 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
this.refreshEvents();
}
/* if (this.splitviewCtrl.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
if (this.splitviewCtrl?.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
// Current selected event was deleted. Clear details.
this.splitviewCtrl.emptyDetails();
}*/
this.emptySplitView();
}
}, this.currentSiteId);
// Update the list when an event is deleted.
@ -183,15 +181,15 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
AddonCalendarProvider.DELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && !data.sent) {
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
this.markAsDeleted(data.eventId, true);
this.deletedEvents.push(data.eventId);
this.hasOffline = true;
} else {
// Event deleted, clear the details if needed and refresh the view.
/* if (this.splitviewCtrl.isOn()) {
this.splitviewCtrl.emptyDetails();
} */
// Event deleted, clear the details if needed and refresh the view.
if (this.splitviewCtrl?.isOn()) {
this.emptySplitView();
}
this.eventsLoaded = false;
this.refreshEvents();
@ -259,14 +257,26 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
await this.fetchData(false, true, false);
/* if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
if (!this.eventId && this.splitviewCtrl?.isOn() && this.events.length > 0) {
// Take first online event and load it. If no online event, load the first offline.
if (this.onlineEvents[0]) {
this.gotoEvent(this.onlineEvents[0].id);
} else {
this.gotoEvent(this.offlineEvents[0].id);
}
}*/
}
}
/**
* Convenience function to clear detail view of the split view.
*/
protected emptySplitView(): void {
// Empty details.
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/event') ||
CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/edit');
if (splitViewLoaded) {
CoreNavigator.instance.navigate('../');
}
}
/**
@ -642,7 +652,10 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
params.courseId = this.filter.courseId;
}
CoreNavigator.instance.navigateToSitePath('/calendar/edit', { params }); // @todo , this.splitviewCtrl);
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/event') ||
CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/edit');
const path = (splitViewLoaded ? '../' : '') + 'edit';
CoreNavigator.instance.navigate(path, { params });
}
/**
@ -664,9 +677,12 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
// It's an offline event, go to the edit page.
this.openEdit(eventId);
} else {
/* this.splitviewCtrl.push('/calendar/event', {
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/event') ||
CoreNavigator.instance.isCurrentPathInTablet('**/calendar/list/edit');
const path = (splitViewLoaded ? '../' : '') + 'event';
CoreNavigator.instance.navigate(path, { params: {
id: eventId,
});*/
} });
}
}

View File

@ -22,6 +22,7 @@ import {
import { CoreDomUtils } from '@services/utils/dom';
import { ActivatedRoute } from '@angular/router';
import { ModalController } from '@singletons';
import { CoreNavigator } from '@services/navigator';
/**
* Component that displays the list of conversations, including group conversations.
@ -49,8 +50,8 @@ export class AddonMessagesConversationInfoComponent implements OnInit {
* Component loaded.
*/
ngOnInit(): void {
this.route.queryParams.subscribe(async params => {
this.conversationId = parseInt(params['conversationId'], 10);
this.route.queryParams.subscribe(async () => {
this.conversationId = CoreNavigator.instance.getRouteNumberParam('conversationId') || 0;
this.loaded = false;
this.fetchData().finally(() => {

View File

@ -18,7 +18,7 @@ import { Route, RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
import { AddonMessagesSettingsHandlerService } from './services/handlers/settings';
export const discussionRoute: Route = {
export const AddonMessagesDiscussionRoute: Route = {
path: 'discussion',
loadChildren: () => import('./pages/discussion/discussion.module')
.then(m => m.AddonMessagesDiscussionPageModule),
@ -40,7 +40,7 @@ function buildRoutes(injector: Injector): Routes {
loadChildren: () => import('./pages/group-conversations/group-conversations.module')
.then(m => m.AddonMessagesGroupConversationsPageModule),
},
discussionRoute,
AddonMessagesDiscussionRoute,
{
path: 'search',
loadChildren: () => import('./pages/search/search.module')

View File

@ -5,7 +5,8 @@
</ion-buttons>
<ion-title>{{ 'addon.messages.contacts' | translate }}</ion-title>
<ion-buttons slot="end">
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
<!-- Add an empty context menu so discussion page can add items in split view,
otherwise the menu disappears in some cases. -->
<core-context-menu></core-context-menu>
</ion-buttons>
</ion-toolbar>
@ -16,7 +17,7 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)"
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch()"
[placeholder]="'addon.messages.contactname' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
[disabled]="!loaded" searchArea="AddonMessagesContacts"></core-search-box>
@ -35,7 +36,8 @@
</ion-item-divider>
<ng-container *ngFor="let contact of contacts[contactType]">
<!-- Don't show deleted users -->
<ion-item class="ion-text-wrap addon-messages-conversation-item" *ngIf="contact.profileimageurl || contact.profileimageurlsmall"
<ion-item class="ion-text-wrap addon-messages-conversation-item"
*ngIf="contact.profileimageurl || contact.profileimageurlsmall"
[title]="contact.fullname" (click)="gotoDiscussion(contact.id)" detail
[class.core-selected-item]="contact.id == discussionUserId">
<core-user-avatar [user]="contact" slot="start" [checkOnline]="contact.showonlinestatus"></core-user-avatar>

View File

@ -18,7 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { conditionalRoutes } from '@/app/app-routing.module';
import { discussionRoute } from '@addons/messages/messages-lazy.module';
import { AddonMessagesDiscussionRoute } from '@addons/messages/messages-lazy.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
@ -26,23 +26,27 @@ import { CoreSearchComponentsModule } from '@features/search/components/componen
import { AddonMessagesContacts35Page } from './contacts.page';
const routes: Routes = [
const mobileRoutes: Routes = [
{
matcher: segments => {
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
return matches ? { consumed: [] } : null;
},
path: '',
component: AddonMessagesContacts35Page,
children: conditionalRoutes([
{
path: '',
pathMatch: 'full',
},
discussionRoute,
], () => CoreScreen.instance.isTablet),
},
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
AddonMessagesDiscussionRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonMessagesContacts35Page,
children: [
AddonMessagesDiscussionRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -87,10 +87,9 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
* Component loaded.
*/
ngOnInit(): void {
this.route.queryParams.subscribe(async params => {
const discussionUserId = params['discussionUserId']
? parseInt(params['discussionUserId'], 10)
: (params['userId'] ? parseInt(params['userId'], 10) : undefined);
this.route.queryParams.subscribe(async () => {
const discussionUserId = CoreNavigator.instance.getRouteNumberParam('discussionUserId') ||
CoreNavigator.instance.getRouteNumberParam('userId') || undefined;
if (this.loaded && this.discussionUserId == discussionUserId) {
return;
@ -250,7 +249,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
userId: discussionUserId,
};
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/contacts-35/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/contacts-35/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
// @todo Check why this is failing on ngInit.

View File

@ -8,7 +8,8 @@
<ion-button (click)="gotoSearch()" [attr.aria-label]="'addon.messages.search' | translate">
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
</ion-button>
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu
disappears in some cases. -->
<core-context-menu></core-context-menu>
</ion-buttons>
</ion-toolbar>
@ -17,10 +18,10 @@
<core-split-view>
<ion-tab-bar class="core-tabs-bar">
<ion-row>
<ion-col class="tab-slide" [attr.aria-selected]="selected == 'confirmed'" (click)="selectTab('confirmed', $event)">
<ion-col class="tab-slide" [attr.aria-selected]="selected == 'confirmed'" (click)="selectTab('confirmed')">
<ion-label>{{ 'addon.messages.contacts' | translate}}</ion-label>
</ion-col>
<ion-col class="tab-slide" [attr.aria-selected]="selected != 'confirmed'" (click)="selectTab('requests', $event)">
<ion-col class="tab-slide" [attr.aria-selected]="selected != 'confirmed'" (click)="selectTab('requests')">
<ion-label>
{{ 'addon.messages.requests' | translate}}
<ion-badge *ngIf="requestsBadge">{{ requestsBadge }}</ion-badge>

View File

@ -18,32 +18,37 @@ import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { conditionalRoutes } from '@/app/app-routing.module';
import { discussionRoute } from '@addons/messages/messages-lazy.module';
import { AddonMessagesDiscussionRoute } from '@addons/messages/messages-lazy.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonMessagesContactsPage } from './contacts.page';
const routes: Routes = [
const mobileRoutes: Routes = [
{
matcher: segments => {
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
return matches ? { consumed: [] } : null;
},
path: '',
component: AddonMessagesContactsPage,
children: conditionalRoutes([
{
path: '',
pathMatch: 'full',
},
discussionRoute,
], () => CoreScreen.instance.isTablet),
},
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
AddonMessagesDiscussionRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonMessagesContactsPage,
children: [
AddonMessagesDiscussionRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({
imports: [
RouterModule.forChild(routes),

View File

@ -258,7 +258,7 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
this.selectedUserId = userId;
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/contacts/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/contacts/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.instance.navigate(path, { params : { userId } });

View File

@ -61,7 +61,7 @@
</core-context-menu>
</core-navbar-buttons>
</ion-header>
<ion-content class="has-footer" (ionScroll)="scrollFunction($event)">
<ion-content class="has-footer" (ionScroll)="scrollFunction()">
<core-loading [hideUntil]="loaded" class="safe-area-page">
<!-- Load previous messages. -->
<core-infinite-loading [enabled]="canLoadMore" (action)="loadPrevious($event)" position="top" [error]="loadMoreError">
@ -72,18 +72,24 @@
<p class="ion-text-center"><i>{{ 'addon.messages.selfconversationdefaultmessage' | translate }}</i></p>
</ng-container>
<ion-list class="addon-messages-discussion-container" [class.addon-messages-discussion-group]="isGroup" [attr.aria-live]="'polite'">
<ion-list class="addon-messages-discussion-container" [class.addon-messages-discussion-group]="isGroup"
[attr.aria-live]="'polite'">
<ng-container *ngFor="let message of messages; index as index; last as last">
<h6 class="ion-text-center addon-messages-date" *ngIf="message.showDate">
{{ message.timecreated | coreFormatDate: "strftimedayshort" }}
</h6>
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom" color="light">
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom"
color="light">
<ion-label>{{ 'addon.messages.newmessages' | translate }}</ion-label>
<ion-icon name="arrow-round-down"></ion-icon>
</ion-chip>
<ion-item class="ion-text-wrap addon-message" (longPress)="copyMessage(message)" [class.addon-message-mine]="message.useridfrom == currentUserId" [class.addon-message-not-mine]="message.useridfrom != currentUserId" [class.addon-message-no-user]="!message.showUserData" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<ion-item class="ion-text-wrap addon-message" (longPress)="copyMessage(message)"
[class.addon-message-mine]="message.useridfrom == currentUserId"
[class.addon-message-not-mine]="message.useridfrom != currentUserId"
[class.addon-message-no-user]="!message.showUserData"
[@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<ion-label>
<!-- User data. -->
<h2 class="addon-message-user">
@ -98,7 +104,8 @@
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
<p class="addon-message-text">
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text" contextLevel="system"
[contextInstanceId]="0"></core-format-text>
</p>
</ion-label>
<ion-button fill="clear" *ngIf="!message.sending && showDelete" (click)="deleteMessage(message, index)"
@ -112,11 +119,12 @@
</ng-container>
</ion-list>
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="far-comments" [message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="far-comments"
[message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
</core-loading>
<!-- Scroll bottom. -->
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="loaded && newMessages > 0">
<ion-fab-button size="small" (click)="scrollToFirstUnreadMessage(true)" color="light"
<ion-fab-button size="small" (click)="scrollToFirstUnreadMessage()" color="light"
[attr.aria-label]="'addon.messages.newmessages' | translate">
<ion-icon name="fas-arrow-down"></ion-icon>
<span class="core-discussion-messages-badge">{{ newMessages }}</span>
@ -125,24 +133,38 @@
</ion-content>
<ion-footer color="light" class="footer-adjustable" *ngIf="loaded && (!conversationId || conversation)">
<ion-toolbar color="light">
<p *ngIf="footerType == 'unable'" class="ion-text-center ion-margin-horizontal">{{ 'addon.messages.unabletomessage' | translate }}</p>
<p *ngIf="footerType == 'unable'" class="ion-text-center ion-margin-horizontal">
{{ 'addon.messages.unabletomessage' | translate }}
</p>
<div *ngIf="footerType == 'blocked'" class="ion-padding-horizontal">
<p class="ion-text-center">{{ 'addon.messages.youhaveblockeduser' | translate }}</p>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="unblockUser()">{{ 'addon.messages.unblockuser' | translate }}</ion-button>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="unblockUser()">
{{ 'addon.messages.unblockuser' | translate }}
</ion-button>
</div>
<div *ngIf="footerType == 'requiresContact'" class="ion-padding-horizontal">
<p class="ion-text-center"><strong>{{ 'addon.messages.isnotinyourcontacts' | translate: {$a: otherMember.fullname} }}</strong></p>
<div *ngIf="footerType == 'requiresContact' && otherMember" class="ion-padding-horizontal">
<p class="ion-text-center">
<strong>{{ 'addon.messages.isnotinyourcontacts' | translate: {$a: otherMember.fullname} }}</strong>
</p>
<p class="ion-text-center">{{ 'addon.messages.requirecontacttomessage' | translate: {$a: otherMember.fullname} }}</p>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="createContactRequest()">{{ 'addon.messages.sendcontactrequest' | translate }}</ion-button>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="createContactRequest()">
{{ 'addon.messages.sendcontactrequest' | translate }}
</ion-button>
</div>
<div *ngIf="footerType == 'requestReceived'" class="ion-padding-horizontal">
<div *ngIf="footerType == 'requestReceived' && otherMember" class="ion-padding-horizontal">
<p class="ion-text-center">{{ 'addon.messages.userwouldliketocontactyou' | translate: {$a: otherMember.fullname} }}</p>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="confirmContactRequest()">{{ 'addon.messages.acceptandaddcontact' | translate }}</ion-button>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" color="light" (click)="declineContactRequest()">{{ 'addon.messages.decline' | translate }}</ion-button>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" (click)="confirmContactRequest()">
{{ 'addon.messages.acceptandaddcontact' | translate }}
</ion-button>
<ion-button expand="block" class="ion-text-wrap ion-margin-bottom" color="light" (click)="declineContactRequest()">
{{ 'addon.messages.decline' | translate }}
</ion-button>
</div>
<div *ngIf="footerType == 'requestSent' || (footerType == 'message' && requestContactSent)" class="ion-padding-horizontal">
<p class="ion-text-center"><strong>{{ 'addon.messages.contactrequestsent' | translate }}</strong></p>
<p class="ion-text-center">{{ 'addon.messages.yourcontactrequestpending' | translate: {$a: otherMember.fullname} }}</p>
<p class="ion-text-center" *ngIf="otherMember">
{{ 'addon.messages.yourcontactrequestpending' | translate: {$a: otherMember.fullname} }}
</p>
</div>
<core-send-message-form *ngIf="footerType == 'message'" (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard"
[placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>

View File

@ -61,7 +61,7 @@ import { AddonMessagesConversationInfoComponent } from '../../components/convers
selector: 'page-addon-messages-discussion',
templateUrl: 'discussion.html',
animations: [CoreAnimations.SLIDE_IN_OUT],
styleUrls: ['discussion.scss', '../../../../theme/messages.scss'],
styleUrls: ['discussion.scss'],
})
export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterViewInit {
@ -167,15 +167,15 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
*/
async ngOnInit(): Promise<void> {
this.route.queryParams.subscribe(async params => {
this.route.queryParams.subscribe(async () => {
// Disable the profile button if we're already coming from a profile.
const backViewPage = CoreNavigator.instance.getPreviousPath();
this.showInfo = !backViewPage || !CoreTextUtils.instance.matchesGlob(backViewPage, '**/user/profile');
this.loaded = false;
this.conversationId = params['conversationId'] ? parseInt(params['conversationId'], 10) : undefined;
this.userId = params['userId'] ? parseInt(params['userId'], 10) : undefined;
this.showKeyboard = !!params['showKeyboard'];
this.conversationId = CoreNavigator.instance.getRouteNumberParam('conversationId') || undefined;
this.userId = CoreNavigator.instance.getRouteNumberParam('userId') || undefined;
this.showKeyboard = CoreNavigator.instance.getRouteBooleanParam('showKeyboard') || false;
await this.fetchData();
@ -1329,7 +1329,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
const result = await modal.onDidDismiss();
if (typeof result.data != 'undefined') {
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/**/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/**/discussion');
// Open user conversation.
if (splitViewLoaded) {

View File

@ -5,7 +5,8 @@
</ion-buttons>
<ion-title>{{ 'addon.messages.messages' | translate }}</ion-title>
<ion-buttons slot="end">
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
<!-- Add an empty context menu so discussion page can add items in split view,
otherwise the menu disappears in some cases. -->
<core-context-menu></core-context-menu>
</ion-buttons>
</ion-toolbar>
@ -16,7 +17,7 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch($event)"
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch()"
[placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
[disabled]="!loaded" searchArea="AddonMessagesDiscussions"></core-search-box>
@ -24,7 +25,7 @@
<ion-list class="ion-no-margin">
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts($event)"
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()"
[attr.aria-label]="'addon.messages.contacts' | translate" detail>
<ion-icon name="fas-address-book" slot="start"></ion-icon>
<ion-label><h2>{{ 'addon.messages.contacts' | translate }}</h2></ion-label>
@ -37,9 +38,9 @@
</ion-label>
<ion-note slot="end" class="ion-padding-end"><ion-badge>{{ search.results.length }}</ion-badge></ion-note>
</ion-item-divider>
<ion-item class="ion-text-wrap" *ngFor="let result of search.results" [title]="result.fullname"
<ion-item class="ion-text-wrap addon-message-discussion" *ngFor="let result of search.results" [title]="result.fullname"
(click)="gotoDiscussion(result.userid, result.messageid)"
[class.core-selected-item]="result.userid == discussionUserId" class="addon-message-discussion">
[class.core-selected-item]="result.userid == discussionUserId">
<core-user-avatar [user]="result" slot="start" [checkOnline]="result.showonlinestatus"></core-user-avatar>
<ion-label>
<h2>{{ result.fullname }}</h2>
@ -49,18 +50,21 @@
</ion-item>
</ng-container>
<ng-container *ngIf="!search.showResults">
<ion-item class="ion-text-wrap" *ngFor="let discussion of discussions" [title]="discussion.fullname"
(click)="gotoDiscussion(discussion.message!.user)"
[class.core-selected-item]="discussion.message!.user == discussionUserId" class="addon-message-discussion">
<ion-item class="ion-text-wrap addon-message-discussion" *ngFor="let discussion of discussions"
[title]="discussion.fullname" (click)="gotoDiscussion(discussion.message!.user)"
[class.core-selected-item]="discussion.message!.user == discussionUserId">
<core-user-avatar [user]="discussion" slot="start" checkOnline="false"></core-user-avatar>
<ion-label>
<h2>{{ discussion.fullname }}</h2>
<ion-note *ngIf="discussion.message!.timecreated > 0 || discussion.unread">
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
<span *ngIf="discussion.message!.timecreated > 0">{{discussion.message!.timecreated / 1000 | coreDateDayOrTime}}</span>
<span *ngIf="discussion.message!.timecreated > 0">
{{discussion.message!.timecreated / 1000 | coreDateDayOrTime}}
</span>
</ion-note>
<p>
<core-format-text clean="true" singleLine="true" [text]="discussion.message!.message" contextLevel="system" [contextInstanceId]="0">
<core-format-text clean="true" singleLine="true" [text]="discussion.message!.message"
contextLevel="system" [contextInstanceId]="0">
</core-format-text>
</p>
</ion-label>

View File

@ -19,30 +19,34 @@ import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CoreScreen } from '@services/screen';
import { conditionalRoutes } from '@/app/app-routing.module';
import { discussionRoute } from '@addons/messages/messages-lazy.module';
import { AddonMessagesDiscussionRoute } from '@addons/messages/messages-lazy.module';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
import { AddonMessagesDiscussions35Page } from './discussions.page';
const routes: Routes = [
const mobileRoutes: Routes = [
{
matcher: segments => {
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
return matches ? { consumed: [] } : null;
},
path: '',
component: AddonMessagesDiscussions35Page,
children: conditionalRoutes([
{
path: '',
pathMatch: 'full',
},
discussionRoute,
], () => CoreScreen.instance.isTablet),
},
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
AddonMessagesDiscussionRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonMessagesDiscussions35Page,
children: [
AddonMessagesDiscussionRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -146,10 +146,9 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
* Component loaded.
*/
ngOnInit(): void {
this.route.queryParams.subscribe(async params => {
const discussionUserId = params['discussionUserId']
? parseInt(params['discussionUserId'], 10)
: (params['userId'] ? parseInt(params['userId'], 10) : undefined);
this.route.queryParams.subscribe(async () => {
const discussionUserId = CoreNavigator.instance.getRouteNumberParam('discussionUserId') ||
CoreNavigator.instance.getRouteNumberParam('userId') || undefined;
if (this.loaded && this.discussionUserId == discussionUserId) {
return;
@ -281,7 +280,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
params.message = messageId;
}
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/index/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/index/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.instance.navigate(path, { params });

View File

@ -8,7 +8,7 @@
<ion-button (click)="gotoSearch()" [attr.aria-label]="'addon.messages.search' | translate">
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
</ion-button>
<ion-button (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
<ion-button (click)="gotoSettings()" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
<ion-icon name="fas-cog" slot="icon-only"></ion-icon>
</ion-button>
<!-- Add an empty context menu so discussion page can add items in split view,
@ -25,14 +25,14 @@
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
<ion-list>
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts($event)"
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()"
[attr.aria-label]="'addon.messages.contacts' | translate" detail>
<ion-icon name="fas-address-book" slot="start"></ion-icon>
<ion-label><h2>{{ 'addon.messages.contacts' | translate }}</h2></ion-label>
<ion-badge *ngIf="contactRequestsCount > 0" slot="end">{{contactRequestsCount}}</ion-badge>
</ion-item>
<!-- Favourite conversations. -->
<ion-item-divider class="ion-text-wrap" (click)="toggle(favourites)" class="core-expandable" sticky="true">
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(favourites)" sticky="true">
<ion-icon *ngIf="!favourites.expanded" name="fas-caret-right" slot="start"></ion-icon>
<ion-icon *ngIf="favourites.expanded" name="fas-caret-down" slot="start"></ion-icon>
<ion-label>{{ 'core.favourites' | translate }} ({{ favourites.count }})</ion-label>
@ -42,7 +42,8 @@
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: favourites.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
<core-infinite-loading [enabled]="favourites.canLoadMore" (action)="loadMoreConversations(favourites, $event)" [error]="favourites.loadMoreError"></core-infinite-loading>
<core-infinite-loading [enabled]="favourites.canLoadMore" (action)="loadMoreConversations(favourites, $event)"
[error]="favourites.loadMoreError"></core-infinite-loading>
<ion-item class="ion-text-wrap" *ngIf="favourites.conversations && favourites.conversations.length == 0">
<ion-label><p>{{ 'addon.messages.nofavourites' | translate }}</p></ion-label>
</ion-item>
@ -52,16 +53,18 @@
</ion-item>
<!-- Group conversations. -->
<ion-item-divider class="ion-text-wrap" (click)="toggle(group)" class="core-expandable" sticky="true">
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(group)" sticky="true">
<ion-icon *ngIf="!group.expanded" name="fas-caret-right" slot="start"></ion-icon>
<ion-icon *ngIf="group.expanded" name="fas-caret-down" slot="start"></ion-icon>
<ion-label>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</ion-label>
<ion-badge slot="end" *ngIf="group.unread">{{ group.unread }}</ion-badge>
</ion-item-divider>
<div [hidden]="!group.conversations || !group.expanded || group.loading" #grouplist>
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: group.conversations}"></ng-container>
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: group.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
<core-infinite-loading [enabled]="group.canLoadMore" (action)="loadMoreConversations(group, $event)" [error]="group.loadMoreError"></core-infinite-loading>
<core-infinite-loading [enabled]="group.canLoadMore" (action)="loadMoreConversations(group, $event)"
[error]="group.loadMoreError"></core-infinite-loading>
<ion-item class="ion-text-wrap" *ngIf="group.conversations && group.conversations.length == 0">
<ion-label><p>{{ 'addon.messages.nogroupconversations' | translate }}</p></ion-label>
</ion-item>
@ -70,16 +73,18 @@
<ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item>
<ion-item-divider class="ion-text-wrap" (click)="toggle(individual)" class="core-expandable" sticky="true">
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(individual)" sticky="true">
<ion-icon *ngIf="!individual.expanded" name="fas-caret-right" slot="start"></ion-icon>
<ion-icon *ngIf="individual.expanded" name="fas-caret-down" slot="start"></ion-icon>
<ion-label>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</ion-label>
<ion-badge slot="end" *ngIf="individual.unread">{{ individual.unread }}</ion-badge>
</ion-item-divider>
<div [hidden]="!individual.conversations || !individual.expanded || individual.loading" #indlist>
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: individual.conversations}"></ng-container>
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: individual.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
<core-infinite-loading [enabled]="individual.canLoadMore" (action)="loadMoreConversations(individual, $event)" [error]="individual.loadMoreError"></core-infinite-loading>
<core-infinite-loading [enabled]="individual.canLoadMore" (action)="loadMoreConversations(individual, $event)"
[error]="individual.loadMoreError"></core-infinite-loading>
<ion-item class="ion-text-wrap" *ngIf="individual.conversations && individual.conversations.length == 0">
<ion-label><p>{{ 'addon.messages.noindividualconversations' | translate }}</p></ion-label>
</ion-item>
@ -95,10 +100,15 @@
<!-- Template to render a list of conversations. -->
<ng-template #conversationsTemplate let-conversations="conversations">
<ion-item class="ion-text-wrap" *ngFor="let conversation of conversations" [title]="conversation.name" (click)="gotoConversation(conversation.id, conversation.userid)" [class.core-selected-item]="(conversation.id && conversation.id == selectedConversationId) || (conversation.userid && conversation.userid == selectedUserId)" class="addon-message-discussion" id="addon-message-conversation-{{ conversation.id ? conversation.id : 'user-' + conversation.userid }}">
<ion-item class="ion-text-wrap addon-message-discussion" *ngFor="let conversation of conversations" [title]="conversation.name"
(click)="gotoConversation(conversation.id, conversation.userid)"
[class.core-selected-item]="(conversation.id && conversation.id == selectedConversationId) ||
(conversation.userid && conversation.userid == selectedUserId)"
id="addon-message-conversation-{{ conversation.id ? conversation.id : 'user-' + conversation.userid }}">
<!-- Group conversation image. -->
<ion-avatar slot="start" *ngIf="conversation.type == typeGroup">
<img [src]="conversation.imageurl" [alt]="conversation.name" core-external-content onError="this.src='assets/img/group-avatar.png'">
<img [src]="conversation.imageurl" [alt]="conversation.name" core-external-content
onError="this.src='assets/img/group-avatar.png'">
</ion-avatar>
<!-- Avatar for individual conversations. -->
@ -108,20 +118,25 @@
<ion-label>
<h2>
<core-format-text [text]="conversation.name" contextLevel="system" [contextInstanceId]="0"></core-format-text>
<ion-icon name="fas-user-slash" *ngIf="conversation.isblocked" [title]="'addon.messages.contactblocked' | translate">
</ion-icon>
<ion-icon *ngIf="conversation.ismuted" name="fas-volume-mute" [title]="'addon.messages.mutedconversation' | translate">
</ion-icon>
<ion-icon name="fas-user-slash" *ngIf="conversation.isblocked"
[title]="'addon.messages.contactblocked' | translate"></ion-icon>
<ion-icon *ngIf="conversation.ismuted" name="fas-volume-mute"
[title]="'addon.messages.mutedconversation' | translate"></ion-icon>
</h2>
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
</ion-note>
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname" contextLevel="system" [contextInstanceId]="0"></core-format-text></p>
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname" contextLevel="system"
[contextInstanceId]="0"></core-format-text></p>
<p class="addon-message-last-message">
<span *ngIf="conversation.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
<span *ngIf="!conversation.sentfromcurrentuser && conversation.type == typeGroup && conversation.members[0]" class="addon-message-last-message-user">{{ conversation.members[0].fullname + ':' }}</span>
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message-text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
<span *ngIf="conversation.sentfromcurrentuser" class="addon-message-last-message-user">
{{ 'addon.messages.you' | translate }}
</span>
<span *ngIf="!conversation.sentfromcurrentuser && conversation.type == typeGroup && conversation.members[0]"
class="addon-message-last-message-user">{{ conversation.members[0].fullname + ':' }}</span>
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage"
class="addon-message-last-message-text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
</p>
</ion-label>
</ion-item>

View File

@ -18,31 +18,34 @@ import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { conditionalRoutes } from '@/app/app-routing.module';
import { discussionRoute } from '@addons/messages/messages-lazy.module';
import { AddonMessagesDiscussionRoute } from '@addons/messages/messages-lazy.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonMessagesGroupConversationsPage } from './group-conversations.page';
const mobileRoutes: Routes = [
{
path: '',
component: AddonMessagesGroupConversationsPage,
},
AddonMessagesDiscussionRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonMessagesGroupConversationsPage,
children: [
AddonMessagesDiscussionRoute,
],
},
];
const routes: Routes = [
{
matcher: segments => {
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
return matches ? { consumed: [] } : null;
},
component: AddonMessagesGroupConversationsPage,
children: conditionalRoutes([
{
path: '',
pathMatch: 'full',
},
discussionRoute,
], () => CoreScreen.instance.isTablet),
},
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -280,11 +280,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* Component loaded.
*/
ngOnInit(): void {
this.route.queryParams.subscribe(async params => {
this.route.queryParams.subscribe(async () => {
// Conversation to load.
this.conversationId = params['conversationId'] ? parseInt(params['conversationId'], 10) : undefined;
this.conversationId = CoreNavigator.instance.getRouteNumberParam('conversationId') || undefined;
if (!this.conversationId) {
this.discussionUserId = params['discussionUserId'] ? parseInt(params['discussionUserId'], 10) : undefined;
this.discussionUserId = CoreNavigator.instance.getRouteNumberParam('discussionUserId') || undefined;
}
if (this.conversationId || this.discussionUserId) {
@ -535,7 +535,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
params.message = messageId;
}
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/group-conversations/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/group-conversations/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.instance.navigate(path, { params });
}

View File

@ -5,14 +5,15 @@
</ion-buttons>
<ion-title>{{ 'addon.messages.searchcombined' | translate }}</ion-title>
<ion-buttons slot="end">
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
<!-- Add an empty context menu so discussion page can add items in split view,
otherwise the menu disappears in some cases. -->
<core-context-menu></core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<core-split-view>
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)" [disabled]="disableSearch" autocorrect="off"
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch()" [disabled]="disableSearch" autocorrect="off"
[spellcheck]="false" [autoFocus]="true" [lengthCheck]="1" searchArea="AddonMessagesSearch"></core-search-box>
<core-loading [hideUntil]="!displaySearching" [message]="'core.searching' | translate">
@ -55,8 +56,12 @@
{{result.lastmessagedate | coreDateDayOrTime}}
</ion-note>
<p class="addon-message-last-message">
<span *ngIf="result.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
<core-format-text clean="true" singleLine="true" [text]="result.lastmessage" [highlight]="result.highlightMessage" contextLevel="system" [contextInstanceId]="0" class="addon-message-last-message-text"></core-format-text>
<span *ngIf="result.sentfromcurrentuser" class="addon-message-last-message-user">
{{ 'addon.messages.you' | translate }}
</span>
<core-format-text clean="true" singleLine="true" [text]="result.lastmessage"
[highlight]="result.highlightMessage" contextLevel="system" [contextInstanceId]="0"
class="addon-message-last-message-text"></core-format-text>
</p>
</ion-label>
</ion-item>

View File

@ -19,30 +19,34 @@ import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CoreScreen } from '@services/screen';
import { conditionalRoutes } from '@/app/app-routing.module';
import { discussionRoute } from '@addons/messages/messages-lazy.module';
import { AddonMessagesDiscussionRoute } from '@addons/messages/messages-lazy.module';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
import { AddonMessagesSearchPage } from './search.page';
const routes: Routes = [
const mobileRoutes: Routes = [
{
matcher: segments => {
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
return matches ? { consumed: [] } : null;
},
path: '',
component: AddonMessagesSearchPage,
children: conditionalRoutes([
{
path: '',
pathMatch: 'full',
},
discussionRoute,
], () => CoreScreen.instance.isTablet),
},
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
AddonMessagesDiscussionRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: AddonMessagesSearchPage,
children: [
AddonMessagesDiscussionRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -108,7 +108,7 @@ export class AddonMessagesSearchPage implements OnDestroy {
this.displayResults = false;
// Empty details.
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/search/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/search/discussion');
if (splitViewLoaded) {
CoreNavigator.instance.navigate('../');
}
@ -258,7 +258,7 @@ export class AddonMessagesSearchPage implements OnDestroy {
params.userId = result.id;
}
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/search/discussion');
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/messages/search/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.instance.navigate(path, { params });
}

View File

@ -20,13 +20,13 @@ import { CoreSettingsDelegate, CoreSettingsHandlerData } from '../../services/se
import { CoreEventObserver, CoreEvents, CoreEventSiteUpdatedData } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
// import { CoreSplitViewComponent } from '@components/split-view/split-view';
// import { CoreSharedFiles } from '@features/sharedfiles/services/sharedfiles';
import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings-helper';
import { CoreApp } from '@services/app';
import { CoreSiteInfo } from '@classes/site';
import { Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator';
import { CoreScreen } from '@services/screen';
/**
* Page that displays the list of site settings pages.
@ -37,8 +37,6 @@ import { CoreNavigator } from '@services/navigator';
})
export class CoreSitePreferencesPage implements OnInit, OnDestroy {
// @ViewChild(CoreSplitViewComponent) splitviewCtrl?: CoreSplitViewComponent;
isIOS: boolean;
selectedPage?: string;
@ -80,13 +78,14 @@ export class CoreSitePreferencesPage implements OnInit, OnDestroy {
if (this.selectedPage) {
this.openHandler(this.selectedPage);
} /* else if (this.splitviewCtrl.isOn()) {
} else if (CoreScreen.instance.isTablet) {
if (this.isIOS) {
this.openHandler('CoreSharedFilesListPage', { manage: true, siteId: this.siteId, hideSitePicker: true });
// @todo
// this.openHandler('CoreSharedFilesListPage', { manage: true, siteId: this.siteId, hideSitePicker: true });
} else if (this.handlers.length > 0) {
this.openHandler(this.handlers[0].page, this.handlers[0].params);
}
}*/
}
});
}

View File

@ -16,6 +16,7 @@ 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';
import { ActivatedRoute } from '@angular/router';
import { CoreTagAreaDelegate } from '../../services/tag-area-delegate';
import { Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator';
@ -49,35 +50,43 @@ export class CoreTagIndexAreaPage implements OnInit {
areaComponent?: Type<unknown>;
loadMoreError = false;
constructor(
protected route: ActivatedRoute,
) { }
/**
* View loaded.
*/
async ngOnInit(): Promise<void> {
this.tagId = CoreNavigator.instance.getRouteNumberParam('tagId') || this.tagId;
this.tagName = CoreNavigator.instance.getRouteParam('tagName') || this.tagName;
this.collectionId = CoreNavigator.instance.getRouteNumberParam('collectionId') || this.collectionId;
this.areaId = CoreNavigator.instance.getRouteNumberParam('areaId') || this.areaId;
this.fromContextId = CoreNavigator.instance.getRouteNumberParam('fromContextId') || this.fromContextId;
this.contextId = CoreNavigator.instance.getRouteNumberParam('contextId') || this.contextId;
this.recursive = CoreNavigator.instance.getRouteBooleanParam('recursive') ?? true;
this.route.queryParams.subscribe(async () => {
this.loaded = false;
this.areaNameKey = CoreNavigator.instance.getRouteParam('areaNameKey') || '';
// Pass the the following parameters to avoid fetching the first page.
this.componentName = CoreNavigator.instance.getRouteParam('componentName');
this.itemType = CoreNavigator.instance.getRouteParam('itemType');
this.items = CoreNavigator.instance.getRouteParam<unknown[]>('items') || [];
this.nextPage = CoreNavigator.instance.getRouteNumberParam('nextPage') || 0;
this.canLoadMore = CoreNavigator.instance.getRouteBooleanParam('canLoadMore') || false;
this.tagId = CoreNavigator.instance.getRouteNumberParam('tagId') || this.tagId;
this.tagName = CoreNavigator.instance.getRouteParam('tagName') || this.tagName;
this.collectionId = CoreNavigator.instance.getRouteNumberParam('collectionId') || this.collectionId;
this.areaId = CoreNavigator.instance.getRouteNumberParam('areaId') || this.areaId;
this.fromContextId = CoreNavigator.instance.getRouteNumberParam('fromContextId') || this.fromContextId;
this.contextId = CoreNavigator.instance.getRouteNumberParam('contextId') || this.contextId;
this.recursive = CoreNavigator.instance.getRouteBooleanParam('recursive') ?? true;
try {
if (!this.componentName || !this.itemType || !this.items.length || this.nextPage == 0) {
await this.fetchData(true);
this.areaNameKey = CoreNavigator.instance.getRouteParam('areaNameKey') || '';
// Pass the the following parameters to avoid fetching the first page.
this.componentName = CoreNavigator.instance.getRouteParam('componentName');
this.itemType = CoreNavigator.instance.getRouteParam('itemType');
this.items = CoreNavigator.instance.getRouteParam<unknown[]>('items') || [];
this.nextPage = CoreNavigator.instance.getRouteNumberParam('nextPage') || 0;
this.canLoadMore = CoreNavigator.instance.getRouteBooleanParam('canLoadMore') || false;
try {
if (!this.componentName || !this.itemType || !this.items.length || this.nextPage == 0) {
await this.fetchData(true);
}
this.areaComponent = await CoreTagAreaDelegate.instance.getComponent(this.componentName!, this.itemType!);
} finally {
this.loaded = true;
}
this.areaComponent = await CoreTagAreaDelegate.instance.getComponent(this.componentName!, this.itemType!);
} finally {
this.loaded = true;
}
});
}
/**

View File

@ -6,26 +6,28 @@
<ion-title>{{ 'core.tag.tag' | translate }}: {{ tagName }}</ion-title>
</ion-toolbar>
</ion-header>
<!--@todo <core-split-view>-->
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<ion-list *ngIf="hasUnsupportedAreas || areas.length">
<ion-item *ngIf="hasUnsupportedAreas" class="core-warning-item">
<ion-icon slot="start" name="fas-exclamation-triangle" color="warning"></ion-icon>
<ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngFor="let area of areas" [title]="area.nameKey | translate"
(click)="openArea(area)" [class.core-selected-item]="area!.id == selectedAreaId">
<ion-label>
<h2>{{ area!.nameKey | translate }}</h2>
</ion-label>
<ion-badge slot="end" *ngIf="area!.badge">{{ area!.badge }}</ion-badge>
</ion-item>
</ion-list>
<core-empty-box icon="fa-tag" *ngIf="!hasUnsupportedAreas && (!areas || !areas.length)"
[message]="'core.tag.noresultsfor' | translate: { $a: tagName }"></core-empty-box>
</core-loading>
<core-split-view>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<ion-list *ngIf="hasUnsupportedAreas || areas.length">
<ion-item *ngIf="hasUnsupportedAreas" class="core-warning-item">
<ion-icon slot="start" name="fas-exclamation-triangle" color="warning"></ion-icon>
<ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngFor="let area of areas" [title]="area.nameKey | translate"
(click)="openArea(area)" [class.core-selected-item]="area!.id == selectedAreaId">
<ion-label>
<h2>{{ area!.nameKey | translate }}</h2>
</ion-label>
<ion-badge slot="end" *ngIf="area!.badge">{{ area!.badge }}</ion-badge>
</ion-item>
</ion-list>
<core-empty-box icon="fa-tag" *ngIf="!hasUnsupportedAreas && (!areas || !areas.length)"
[message]="'core.tag.noresultsfor' | translate: { $a: tagName }"></core-empty-box>
</core-loading>
</core-split-view>
</ion-content>

View File

@ -17,15 +17,34 @@ import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { conditionalRoutes } from '@/app/app-routing.module';
import { CoreTagIndexAreaRoute } from '@features/tag/tag-lazy.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreTagIndexPage } from './index.page';
const routes: Routes = [
const mobileRoutes: Routes = [
{
path: '',
component: CoreTagIndexPage,
},
CoreTagIndexAreaRoute,
];
const tabletRoutes: Routes = [
{
path: '',
component: CoreTagIndexPage,
children: [
CoreTagIndexAreaRoute,
],
},
];
const routes: Routes = [
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
];
@NgModule({

View File

@ -15,9 +15,9 @@
import { Component, OnInit } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
// import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreTag } from '@features/tag/services/tag';
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
import { CoreScreen } from '@services/screen';
import { CoreNavigator } from '@services/navigator';
/**
@ -29,8 +29,6 @@ import { CoreNavigator } from '@services/navigator';
})
export class CoreTagIndexPage implements OnInit {
// @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
tagId = 0;
tagName = '';
collectionId = 0;
@ -42,7 +40,7 @@ export class CoreTagIndexPage implements OnInit {
selectedAreaId?: number;
hasUnsupportedAreas = false;
areas: (CoreTagAreaDisplay | null)[] = [];
areas: CoreTagAreaDisplay[] = [];
/**
* View loaded.
@ -58,10 +56,10 @@ export class CoreTagIndexPage implements OnInit {
try {
await this.fetchData();
/* if (this.splitviewCtrl.isOn() && this.areas && this.areas.length > 0) {
if (CoreScreen.instance.isTablet && this.areas && this.areas.length > 0) {
const area = this.areas.find((area) => area.id == this.areaId);
this.openArea(area || this.areas[0]);
}*/
}
} finally {
this.loaded = true;
}
@ -88,17 +86,19 @@ export class CoreTagIndexPage implements OnInit {
this.areas = [];
this.hasUnsupportedAreas = false;
const areasDisplay: (CoreTagAreaDisplay | null)[] = await Promise.all(areas.map(async (area) => {
const areasDisplay: CoreTagAreaDisplay[] = [];
await Promise.all(areas.map(async (area) => {
const items = await CoreTagAreaDelegate.instance.parseContent(area.component, area.itemtype, area.content);
if (!items || !items.length) {
// Tag area not supported, skip.
this.hasUnsupportedAreas = true;
return null;
return;
}
return {
areasDisplay.push({
id: area.ta,
componentName: area.component,
itemType: area.itemtype,
@ -106,10 +106,10 @@ export class CoreTagIndexPage implements OnInit {
items,
canLoadMore: !!area.nextpageurl,
badge: items && items.length ? items.length + (area.nextpageurl ? '+' : '') : '',
};
});
}));
this.areas = areasDisplay.filter((area) => area != null);
this.areas = areasDisplay;
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading tag index');
@ -160,8 +160,11 @@ export class CoreTagIndexPage implements OnInit {
canLoadMore: area.canLoadMore,
nextPage: 1,
};
// this.splitviewCtrl.push('index-area', params);
CoreNavigator.instance.navigate('../index-area', { params });
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/tag/index/index-area');
const path = (splitViewLoaded ? '../' : '') + 'index-area';
CoreNavigator.instance.navigate(path, { params });
}
}

View File

@ -13,10 +13,16 @@
// limitations under the License.
import { Injector, NgModule } from '@angular/core';
import { RouterModule, ROUTES, Routes } from '@angular/router';
import { Route, RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
export const CoreTagIndexAreaRoute: Route = {
path: 'index-area',
loadChildren: () =>
import('@features/tag/pages/index-area/index-area.page.module').then(m => m.CoreTagIndexAreaPageModule),
};
function buildRoutes(injector: Injector): Routes {
return [
{
@ -27,11 +33,7 @@ function buildRoutes(injector: Injector): Routes {
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),
},
CoreTagIndexAreaRoute,
...buildTabMainRoutes(injector, {
redirectTo: 'search',
pathMatch: 'full',