Merge pull request #3247 from dpalou/MOBILE-3833

Mobile 3833
main
Noel De Martin 2022-04-12 11:05:15 +02:00 committed by GitHub
commit 0708081868
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 145 additions and 36 deletions

14
package-lock.json generated
View File

@ -87,7 +87,7 @@
"hammerjs": "2.0.8",
"jszip": "3.7.1",
"mathjax": "2.7.7",
"moment": "2.29.0",
"moment": "2.29.2",
"nl.kingsquare.cordova.background-audio": "1.0.1",
"rxjs": "6.5.5",
"ts-md5": "1.2.7",
@ -20984,9 +20984,9 @@
"optional": true
},
"node_modules/moment": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz",
"integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==",
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"engines": {
"node": "*"
}
@ -47321,9 +47321,9 @@
"optional": true
},
"moment": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz",
"integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA=="
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
},
"move-concurrently": {
"version": "1.0.1",

View File

@ -116,7 +116,7 @@
"hammerjs": "2.0.8",
"jszip": "3.7.1",
"mathjax": "2.7.7",
"moment": "2.29.0",
"moment": "2.29.2",
"nl.kingsquare.cordova.background-audio": "1.0.1",
"rxjs": "6.5.5",
"ts-md5": "1.2.7",

View File

@ -56,6 +56,8 @@
<core-format-text [text]="discussion.subject" contextLevel="module" [contextInstanceId]="module && module.id"
[courseId]="courseId">
</core-format-text>
<ion-icon name="fas-lock" *ngIf="discussion.locked" class="addon-mod-forum-locked-icon"
[attr.aria-label]="'addon.mod_forum.discussionlocked' | translate"></ion-icon>
</p>
<ion-button *ngIf="canPin || discussion.canlock || discussion.canfavourite" fill="clear"
[attr.aria-label]="('core.displayoptions' | translate)" (click)="showOptionsMenu($event, discussion)">

View File

@ -18,6 +18,11 @@
ion-icon {
@include margin(0, 6px, 0, 0);
&.addon-mod-forum-locked-icon {
@include margin-horizontal(4px, 0px);
color: var(--gray-500);
}
}
}

View File

@ -12,11 +12,6 @@
<p class="item-heading" *ngIf="offlinePost">{{ 'core.discard' | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="wordCount">
<ion-label>
<p class="item-heading">{{ 'core.numwords' | translate: {'$a': wordCount} }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" [href]="url" *ngIf="url" core-link capture="false" button detail="false" [showBrowserWarning]="false">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label>

View File

@ -34,7 +34,6 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
@Input() cmId!: number;
@Input() forumId!: number; // The forum Id.
wordCount?: number | null; // Number of words when available.
canEdit = false;
canDelete = false;
loaded = false;
@ -89,7 +88,6 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
this.canDelete = !!this.post.capabilities.delete && AddonModForum.isDeletePostAvailable();
this.canEdit = !!this.post.capabilities.edit && AddonModForum.isUpdatePostAvailable();
this.wordCount = (this.post.haswordcount && this.post.wordcount) || null;
this.loaded = true;
}

View File

@ -63,6 +63,9 @@
<core-format-text [component]="component" [componentId]="componentId" [text]="post.message" contextLevel="module"
[contextInstanceId]="forum && forum.cmid" [courseId]="courseId">
</core-format-text>
<p *ngIf="post.haswordcount && post.wordcount">
<em>{{ 'core.numwords' | translate: {'$a': post.wordcount} }}</em>
</p>
<div *ngIf="post.attachments && post.attachments.length > 0">
<core-files [files]="post.attachments" [component]="component" [componentId]="componentId" showInline="true">
</core-files>

View File

@ -41,6 +41,10 @@ const appRoutes: Routes = [
canActivate: [CoreMainMenuAuthGuard],
canLoad: [CoreMainMenuAuthGuard],
},
{
path: 'reload',
loadChildren: () => import('./pages/reload/reload.module').then( m => m.CoreMainMenuReloadPageModule),
},
];
@NgModule({

View File

@ -30,6 +30,7 @@ import { NavigationEnd } from '@angular/router';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { CoreSites } from '@services/sites';
import { CoreDom } from '@singletons/dom';
import { CoreLogger } from '@singletons/logger';
const ANIMATION_DURATION = 500;
@ -84,6 +85,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
protected urlToOpen?: string;
protected redirectPath?: string;
protected redirectOptions?: CoreNavigationOptions;
protected logger: CoreLogger;
@ViewChild('mainTabs') mainTabs?: IonTabs;
@ -92,6 +94,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
constructor() {
this.backButtonFunction = this.backButtonClicked.bind(this);
this.tabAction = new CoreMainMenuRoleTab(this);
this.logger = CoreLogger.getInstance('CoreMainMenuPage');
// Listen navigation events to show or hide tabs.
this.navSubscription = Router.events
@ -193,6 +196,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
const tabPage = this.tabs[0] ? this.tabs[0].page : this.morePageName;
const tabPageParams = this.tabs[0] ? this.tabs[0].pageParams : {};
this.logger.debug(`Select first tab: ${tabPage}.`, this.tabs);
// Use navigate instead of mainTabs.select to be able to pass page params.
CoreNavigator.navigate(tabPage, {

View File

@ -0,0 +1,3 @@
<ion-content>
<core-loading></core-loading>
</ion-content>

View File

@ -0,0 +1,37 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreMainMenuReloadPage } from './reload';
const routes: Routes = [
{
path: '',
component: CoreMainMenuReloadPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
],
declarations: [
CoreMainMenuReloadPage,
],
exports: [RouterModule],
})
export class CoreMainMenuReloadPageModule {}

View File

@ -0,0 +1,36 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit } from '@angular/core';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays a loading and then opens the main menu again.
*/
@Component({
selector: 'page-core-mainmenu-reload',
templateUrl: 'reload.html',
})
export class CoreMainMenuReloadPage implements OnInit {
/**
* @inheritdoc
*/
ngOnInit(): void {
CoreNavigator.navigate('/main', {
reset: true,
});
}
}

View File

@ -26,6 +26,7 @@ import { Diagnostic, Translate } from '@singletons';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { AlertButton } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays the general settings.
@ -168,7 +169,10 @@ export class CoreSettingsGeneralPage {
await CoreUtils.ignoreErrors(Promise.all(sites.map((site) => site.invalidateWsCache())));
CoreEvents.trigger(CoreEvents.LANGUAGE_CHANGED, this.selectedLanguage);
window.location.reload();
CoreNavigator.navigate('/reload', {
reset: true,
});
}
/**

View File

@ -76,7 +76,7 @@ export class CoreTimeUtilsProvider {
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('m', 60);
moment.relativeTimeThreshold('h', 24);
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('d', 30);
moment.relativeTimeThreshold('M', 12);
moment.relativeTimeThreshold('y', 365);
moment.relativeTimeThreshold('ss', 0); // To display exact number of seconds instead of just "a few seconds".

View File

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import moment from 'moment';
import { Translate } from '@singletons';
import { CoreConstants } from '../constants';
/**
* Singleton with helper functions for time operations.
@ -27,37 +28,54 @@ export class CoreTime {
* @return Seconds in a human readable format.
*/
static formatTime(seconds: number, precision = 2): string {
precision = precision || 6; // Use max precision if 0 is passed.
precision = precision || 5; // Use max precision if 0 is passed.
const eventDuration = moment.duration(Math.abs(seconds), 'seconds');
let durationString = '';
const totalSecs = Math.abs(seconds);
if (!totalSecs) {
return Translate.instant('core.now');
}
if (precision && eventDuration.years() > 0) {
durationString += ' ' + moment.duration(eventDuration.years(), 'years').humanize();
const years = Math.floor(totalSecs / CoreConstants.SECONDS_YEAR);
let remainder = totalSecs - (years * CoreConstants.SECONDS_YEAR);
const days = Math.floor(remainder / CoreConstants.SECONDS_DAY);
remainder = totalSecs - (days * CoreConstants.SECONDS_DAY);
const hours = Math.floor(remainder / CoreConstants.SECONDS_HOUR);
remainder = remainder - (hours * CoreConstants.SECONDS_HOUR);
const mins = Math.floor(remainder / CoreConstants.SECONDS_MINUTE);
const secs = remainder - (mins * CoreConstants.SECONDS_MINUTE);
const secondsUnit = Translate.instant('core.' + (secs === 1 ? 'sec' : 'secs'));
const minutesUnit = Translate.instant('core.' + (mins === 1 ? 'min' : 'mins'));
const hoursUnit = Translate.instant('core.' + (hours === 1 ? 'hour' : 'hours'));
const daysUnit = Translate.instant('core.' + (days === 1 ? 'day' : 'days'));
const yearsUnit = Translate.instant('core.' + (years === 1 ? 'year' : 'years'));
const parts: string[] = [];
if (precision && years) {
parts.push(`${years} ${yearsUnit}`);
precision--;
}
if (precision && eventDuration.months() > 0) {
durationString += ' ' + moment.duration(eventDuration.months(), 'months').humanize();
if (precision && days) {
parts.push(`${days} ${daysUnit}`);
precision--;
}
if (precision && eventDuration.days() > 0) {
durationString += ' ' + moment.duration(eventDuration.days(), 'days').humanize();
if (precision && hours) {
parts.push(`${hours} ${hoursUnit}`);
precision--;
}
if (precision && eventDuration.hours() > 0) {
durationString += ' ' + moment.duration(eventDuration.hours(), 'hours').humanize();
if (precision && mins) {
parts.push(`${mins} ${minutesUnit}`);
precision--;
}
if (precision && eventDuration.minutes() > 0) {
durationString += ' ' + moment.duration(eventDuration.minutes(), 'minutes').humanize();
precision--;
}
if (precision && (eventDuration.seconds() > 0 || !durationString)) {
durationString += ' ' + moment.duration(eventDuration.seconds(), 'seconds').humanize();
if (precision && secs) {
parts.push(`${secs} ${secondsUnit}`);
precision--;
}
return durationString.trim();
return parts.join(' ');
}
/**