MOBILE-3565 settings: Add space usage page
parent
a9e8213026
commit
d5e95ccd89
|
@ -0,0 +1,46 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'core.settings.spaceusage' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<!-- @todo <core-navbar-buttons></core-navbar-buttons>-->
|
||||
<ion-button (click)="showInfo()" [attr.aria-label]="'core.info' | translate">
|
||||
<ion-icon name="fas-info-circle" slot="icon-only"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [disabled]="!loaded" (ionRefresh)="refreshData($event)" slot="fixed">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-item *ngFor="let site of sites" [class.core-selected-item]="site.id == currentSiteId">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>
|
||||
<core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text>
|
||||
</h2>
|
||||
<p class="ion-text-wrap">{{ site.fullName }}</p>
|
||||
<p>{{ site.siteUrl }}</p>
|
||||
</ion-label>
|
||||
<p *ngIf="site.spaceUsage != null" slot="end">
|
||||
{{ site.spaceUsage | coreBytesToSize }}
|
||||
</p>
|
||||
<ion-button fill="clear" color="danger" slot="end" (click)="deleteSiteStorage(site)"
|
||||
[hidden]="site.spaceUsage! + site.cacheEntries! <= 0"
|
||||
[attr.aria-label]="'core.settings.deletesitefilestitle' | translate">
|
||||
<ion-icon name="fas-trash" slot="icon-only"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.total' | translate }}</h2>
|
||||
</ion-label>
|
||||
<p slot="end" class="ion-margin-end">
|
||||
{{ totals.spaceUsage | coreBytesToSize }}
|
||||
</p>
|
||||
</ion-item-divider>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,49 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
|
||||
import { CoreSettingsSpaceUsagePage } from './space-usage.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreSettingsSpaceUsagePage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreSettingsSpaceUsagePage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreSettingsSpaceUsagePageModule {}
|
|
@ -0,0 +1,157 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Translate } from '@singletons/core.singletons';
|
||||
import { CoreEventObserver, CoreEvents, CoreEventSiteUpdatedData } from '@singletons/events';
|
||||
|
||||
import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings.helper';
|
||||
|
||||
/**
|
||||
* Page that displays the space usage settings.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-app-settings-space-usage',
|
||||
templateUrl: 'space-usage.html',
|
||||
})
|
||||
export class CoreSettingsSpaceUsagePage implements OnInit, OnDestroy {
|
||||
|
||||
loaded = false;
|
||||
sites: CoreSiteBasicInfoWithUsage[] = [];
|
||||
currentSiteId = '';
|
||||
totals: CoreSiteSpaceUsage = {
|
||||
cacheEntries: 0,
|
||||
spaceUsage: 0,
|
||||
};
|
||||
|
||||
protected sitesObserver: CoreEventObserver;
|
||||
|
||||
constructor() {
|
||||
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async (data: CoreEventSiteUpdatedData) => {
|
||||
const site = await CoreSites.instance.getSite(data.siteId);
|
||||
|
||||
const siteEntry = this.sites.find((siteEntry) => siteEntry.id == site.id);
|
||||
if (siteEntry) {
|
||||
const siteInfo = site.getInfo();
|
||||
|
||||
siteEntry.siteName = site.getSiteName();
|
||||
|
||||
if (siteInfo) {
|
||||
siteEntry.siteUrl = siteInfo.siteurl;
|
||||
siteEntry.fullName = siteInfo.fullname;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.loadSiteData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to load site data/usage and calculate the totals.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async loadSiteData(): Promise<void> {
|
||||
// Calculate total usage.
|
||||
let totalSize = 0;
|
||||
let totalEntries = 0;
|
||||
|
||||
this.sites = await CoreSites.instance.getSortedSites();
|
||||
|
||||
const settingsHelper = CoreSettingsHelper.instance;
|
||||
|
||||
// Get space usage.
|
||||
await Promise.all(this.sites.map(async (site) => {
|
||||
const siteInfo = await settingsHelper.getSiteSpaceUsage(site.id);
|
||||
|
||||
site.cacheEntries = siteInfo.cacheEntries;
|
||||
site.spaceUsage = siteInfo.spaceUsage;
|
||||
|
||||
totalSize += site.spaceUsage || 0;
|
||||
totalEntries += site.cacheEntries || 0;
|
||||
}));
|
||||
|
||||
this.totals.spaceUsage = totalSize;
|
||||
this.totals.cacheEntries = totalEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data.
|
||||
*
|
||||
* @param event Refresher event.
|
||||
*/
|
||||
refreshData(event?: CustomEvent<IonRefresher>): void {
|
||||
this.loadSiteData().finally(() => {
|
||||
event?.detail.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files of a site and the tables that can be cleared.
|
||||
*
|
||||
* @param siteData Site object with space usage.
|
||||
*/
|
||||
async deleteSiteStorage(siteData: CoreSiteBasicInfoWithUsage): Promise<void> {
|
||||
try {
|
||||
const newInfo = await CoreSettingsHelper.instance.deleteSiteStorage(siteData.siteName || '', siteData.id);
|
||||
|
||||
this.totals.spaceUsage -= siteData.spaceUsage! - newInfo.spaceUsage;
|
||||
this.totals.spaceUsage -= siteData.cacheEntries! - newInfo.cacheEntries;
|
||||
|
||||
siteData.spaceUsage = newInfo.spaceUsage;
|
||||
siteData.cacheEntries = newInfo.cacheEntries;
|
||||
} catch {
|
||||
// Ignore cancelled confirmation modal.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show information about space usage actions.
|
||||
*/
|
||||
showInfo(): void {
|
||||
CoreDomUtils.instance.showAlert(
|
||||
Translate.instance.instant('core.help'),
|
||||
Translate.instance.instant('core.settings.spaceusagehelp'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.sitesObserver?.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic site info with space usage and cache entries that can be erased.
|
||||
*/
|
||||
export interface CoreSiteBasicInfoWithUsage extends CoreSiteBasicInfo {
|
||||
cacheEntries?: number; // Number of cached entries that can be cleared.
|
||||
spaceUsage?: number; // Space used in this site.
|
||||
}
|
|
@ -31,8 +31,8 @@ import { makeSingleton, Translate } from '@singletons/core.singletons';
|
|||
* Object with space usage and cache entries that can be erased.
|
||||
*/
|
||||
export interface CoreSiteSpaceUsage {
|
||||
cacheEntries?: number; // Number of cached entries that can be cleared.
|
||||
spaceUsage?: number; // Space used in this site (total files + estimate of cache).
|
||||
cacheEntries: number; // Number of cached entries that can be cleared.
|
||||
spaceUsage: number; // Space used in this site (total files + estimate of cache).
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,6 +24,12 @@ const routes: Routes = [
|
|||
path: 'general',
|
||||
loadChildren: () => import('./pages/general/general.page.module').then( m => m.CoreSettingsGeneralPageModule),
|
||||
},
|
||||
{
|
||||
path: 'spaceusage',
|
||||
loadChildren: () =>
|
||||
import('@core/settings/pages/space-usage/space-usage.page.module')
|
||||
.then(m => m.CoreSettingsSpaceUsagePageModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./pages/app/app.page.module').then( m => m.CoreSettingsAppPageModule),
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
// (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 { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
|
||||
/**
|
||||
* Pipe to turn a number in bytes to a human readable size (e.g. 5,25 MB).
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'coreBytesToSize',
|
||||
})
|
||||
export class CoreBytesToSizePipe implements PipeTransform {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreBytesToSizePipe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a number and turns it to a human readable size.
|
||||
*
|
||||
* @param value The bytes to convert.
|
||||
* @return Readable bytes.
|
||||
*/
|
||||
transform(value: number | string): string {
|
||||
if (typeof value == 'string') {
|
||||
// Convert the value to a number.
|
||||
const numberValue = parseInt(value, 10);
|
||||
if (isNaN(numberValue)) {
|
||||
this.logger.error('Invalid value received', value);
|
||||
|
||||
return value;
|
||||
}
|
||||
value = numberValue;
|
||||
}
|
||||
|
||||
return CoreTextUtils.instance.bytesToSize(value);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@ import { CoreCreateLinksPipe } from './create-links.pipe';
|
|||
import { CoreFormatDatePipe } from './format-date.pipe';
|
||||
import { CoreNoTagsPipe } from './no-tags.pipe';
|
||||
import { CoreTimeAgoPipe } from './time-ago.pipe';
|
||||
import { CoreBytesToSizePipe } from './bytes-to-size.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -24,6 +25,7 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
|
|||
CoreNoTagsPipe,
|
||||
CoreTimeAgoPipe,
|
||||
CoreFormatDatePipe,
|
||||
CoreBytesToSizePipe,
|
||||
],
|
||||
imports: [],
|
||||
exports: [
|
||||
|
@ -31,6 +33,7 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
|
|||
CoreNoTagsPipe,
|
||||
CoreTimeAgoPipe,
|
||||
CoreFormatDatePipe,
|
||||
CoreBytesToSizePipe,
|
||||
],
|
||||
})
|
||||
export class CorePipesModule {}
|
||||
|
|
|
@ -1038,7 +1038,7 @@ export class CoreSitesProvider {
|
|||
id: site.id,
|
||||
siteUrl: site.siteUrl,
|
||||
fullName: siteInfo?.fullname,
|
||||
siteName: CoreConstants.CONFIG.sitename ?? siteInfo?.sitename,
|
||||
siteName: CoreConstants.CONFIG.sitename == '' ? siteInfo?.sitename: CoreConstants.CONFIG.sitename,
|
||||
avatar: siteInfo?.userpictureurl,
|
||||
siteHomeId: siteInfo?.siteid || 1,
|
||||
};
|
||||
|
@ -1055,8 +1055,9 @@ export class CoreSitesProvider {
|
|||
* @param ids IDs of the sites to get. If not defined, return all sites.
|
||||
* @return Promise resolved when the sites are retrieved.
|
||||
*/
|
||||
getSortedSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
|
||||
return this.getSites(ids).then((sites) => {
|
||||
async getSortedSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
|
||||
const sites = await this.getSites(ids);
|
||||
|
||||
// Sort sites by url and ful lname.
|
||||
sites.sort((a, b) => {
|
||||
// First compare by site url without the protocol.
|
||||
|
@ -1080,7 +1081,6 @@ export class CoreSitesProvider {
|
|||
});
|
||||
|
||||
return sites;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Params } from '@angular/router';
|
|||
import { Subject } from 'rxjs';
|
||||
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreSiteInfoResponse } from '@classes/site';
|
||||
|
||||
/**
|
||||
* Observer instance to stop listening to an event.
|
||||
|
@ -192,13 +193,24 @@ export class CoreEvents {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Some events contains siteId added by the trigger function. This type is intended to be combined with others.
|
||||
*/
|
||||
export type CoreEventSiteData = {
|
||||
siteId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data passed to SITE_UPDATED event.
|
||||
*/
|
||||
export type CoreEventSiteUpdatedData = CoreEventSiteData & CoreSiteInfoResponse;
|
||||
|
||||
/**
|
||||
* Data passed to SESSION_EXPIRED event.
|
||||
*/
|
||||
export type CoreEventSessionExpiredData = {
|
||||
export type CoreEventSessionExpiredData = CoreEventSiteData & {
|
||||
pageName?: string;
|
||||
params?: Params;
|
||||
siteId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -105,6 +105,13 @@ ion-list.list-md {
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
// Item styles
|
||||
.item.core-selected-item {
|
||||
// TODO: Add safe are to border and RTL
|
||||
border-inline-start: var(--selected-item-border-width) solid var(--selected-item-color);
|
||||
--ion-safe-area-left: calc(-1 * var(--selected-item-border-width));
|
||||
}
|
||||
|
||||
// Avatar
|
||||
// -------------------------
|
||||
// Large centered avatar
|
||||
|
|
|
@ -131,6 +131,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
--selected-item-color: var(--custom-selected-item-color, var(--core-color));
|
||||
--selected-item-border-width: var(--custom-selected-item-border-width, 5px);
|
||||
|
||||
--drop-shadow: var(--custom-drop-shadow, 0, 0, 0, 0.2);
|
||||
|
||||
--core-login-background: var(--custom-login-background, var(--white));
|
||||
|
|
Loading…
Reference in New Issue