MOBILE-2253 login: Implement sites page

main
Dani Palou 2017-11-30 10:21:03 +01:00
parent 847359bb2a
commit acdf6b3043
10 changed files with 250 additions and 18 deletions

View File

@ -119,3 +119,8 @@ ion-icon.icon-accessory {
padding-bottom: 10px; padding-bottom: 10px;
font-style: italic; font-style: italic;
} }
.content {
// Set bgcolor in content instead of overriding $background-color because that variable is applied to a lot of places.
background-color: $gray-light;
}

View File

@ -0,0 +1,30 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'mm.settings.sites' | translate }}</ion-title>
<ion-buttons end>
<button *ngIf="sites && sites.length > 0" ion-button icon-only (click)="toggleDelete()" [attr.aria-label]="'mm.core.delete' | translate">
<ion-icon name="create" ios="md-create"></ion-icon>
</button>
<button ion-button icon-only (click)="add()" [attr.aria-label]="'mm.core.add' | translate">
<ion-icon name="add"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index">
<ion-avatar item-start>
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'mm.core.pictureof' | translate:{$a: site.fullname} }}" role="presentation">
</ion-avatar>
<h2>{{site.fullName}}</h2>
<p><core-format-text [text]="site.siteName" clean="true" watch="true" [siteId]="site.id"></core-format-text></p>
<p>{{site.siteUrl}}</p>
<ion-badge item-end *ngIf="!showDelete && site.badge">{{site.badge}}</ion-badge>
<button *ngIf="showDelete" item-end ion-button icon-only clear color="danger" (click)="deleteSite($event, idx)" [attr.aria-label]="'mm.core.delete' | translate">
<ion-icon name="trash"></ion-icon>
</button>
</ion-item>
</ion-list>
</ion-content>

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreLoginSitesPage } from './sites';
import { CoreLoginModule } from '../../login.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
@NgModule({
declarations: [
CoreLoginSitesPage,
],
imports: [
CoreDirectivesModule,
CoreLoginModule,
IonicPageModule.forChild(CoreLoginSitesPage),
TranslateModule.forChild()
],
})
export class CoreLoginSitesPageModule {}

View File

@ -0,0 +1,3 @@
page-core-login-sites {
}

View File

@ -0,0 +1,150 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreLoggerProvider } from '../../../../providers/logger';
import { CoreSitesProvider, CoreSiteBasicInfo } from '../../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
import { CoreLoginHelperProvider } from '../../providers/helper';
/**
* Page that displays the list of stored sites.
*/
@IonicPage()
@Component({
selector: 'page-core-login-sites',
templateUrl: 'sites.html',
})
export class CoreLoginSitesPage {
sites: CoreSiteBasicInfo[];
showDelete: boolean;
protected logger;
constructor(private navCtrl: NavController, private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider,
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
private translate: TranslateService, logger: CoreLoggerProvider) {
this.logger = logger.getInstance('CoreLoginSitesPage');
}
/**
* View loaded.
*/
ionViewDidLoad() {
this.sitesProvider.getSites().then((sites) => {
// Remove protocol from the url to show more url text.
sites = sites.map((site) => {
site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '');
site.badge = 10;
// @todo: Implement it once push notifications addon is implemented.
// if ($mmaPushNotifications) {
// $mmaPushNotifications.getSiteCounter(site.id).then(function(number) {
// site.badge = number;
// });
// }
return site;
});
// Sort sites by url and fullname.
this.sites = sites.sort((a, b) => {
// First compare by site url without the protocol.
let compareA = a.siteUrl.toLowerCase(),
compareB = b.siteUrl.toLowerCase(),
compare = compareA.localeCompare(compareB);
if (compare !== 0) {
return compare;
}
// If site url is the same, use fullname instead.
compareA = a.fullName.toLowerCase().trim();
compareB = b.fullName.toLowerCase().trim();
return compareA.localeCompare(compareB);
});
this.showDelete = false;
}).catch(() => {
// Shouldn't happen.
});
}
/**
* Go to the page to add a site.
*/
add() : void {
this.loginHelper.goToAddSite(this.navCtrl, false);
}
/**
* Delete a site.
*
* @param {Event} e Click event.
* @param {number} index Position of the site.
*/
deleteSite(e: Event, index: number) : void {
e.stopPropagation();
let site = this.sites[index],
siteName = site.siteName;
this.textUtils.formatText(siteName).then((siteName) => {
this.domUtils.showConfirm(this.translate.instant('mm.login.confirmdeletesite', {sitename: siteName})).then(() => {
this.sitesProvider.deleteSite(site.id).then(() => {
this.sites.splice(index, 1);
this.showDelete = false;
// If there are no sites left, go to add site.
this.sitesProvider.hasNoSites().then(() => {
this.loginHelper.goToAddSite(this.navCtrl, true);
});
}).catch((error) => {
this.logger.error('Error deleting site ' + site.id, error);
this.domUtils.showErrorModalDefault(error, 'Delete site failed.');
this.domUtils.showErrorModal('mm.login.errordeletesite', true);
});
}).catch(() => {
// User cancelled, nothing to do.
});
});
}
/**
* Login in a site.
*
* @param {string} siteId The site ID.
*/
login(siteId: string) : void {
let modal = this.domUtils.showModalLoading();
this.sitesProvider.loadSite(siteId).then(() => {
if (!this.loginHelper.isSiteLoggedOut()) {
return this.loginHelper.goToSiteInitialPage(this.navCtrl, true);
}
}).catch((error) => {
this.logger.error('Error loading site ' + siteId, error);
this.domUtils.showErrorModalDefault(error, 'Error loading site.');
}).finally(() => {
modal.dismiss();
});
}
/**
* Toggle delete.
*/
toggleDelete() : void {
this.showDelete = !this.showDelete;
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core'; import { Directive, Input, AfterViewInit, ElementRef } from '@angular/core';
import { Platform } from 'ionic-angular'; import { Platform } from 'ionic-angular';
import { CoreAppProvider } from '../providers/app'; import { CoreAppProvider } from '../providers/app';
import { CoreLoggerProvider } from '../providers/logger'; import { CoreLoggerProvider } from '../providers/logger';
@ -32,7 +32,7 @@ import { CoreUrlUtilsProvider } from '../providers/utils/url';
@Directive({ @Directive({
selector: '[core-external-content]' selector: '[core-external-content]'
}) })
export class CoreExternalContentDirective implements OnInit { export class CoreExternalContentDirective implements AfterViewInit {
@Input() siteId?: string; // Site ID to use. @Input() siteId?: string; // Site ID to use.
@Input() component?: string; // Component to link the file to. @Input() component?: string; // Component to link the file to.
@Input() componentId?: string|number; // Component ID to use in conjunction with the component. @Input() componentId?: string|number; // Component ID to use in conjunction with the component.
@ -49,9 +49,9 @@ export class CoreExternalContentDirective implements OnInit {
} }
/** /**
* Function executed when the component is initialized. * View has been initialized
*/ */
ngOnInit() { ngAfterViewInit() {
let currentSite = this.sitesProvider.getCurrentSite(), let currentSite = this.sitesProvider.getCurrentSite(),
siteId = this.siteId || (currentSite && currentSite.getId()), siteId = this.siteId || (currentSite && currentSite.getId()),
targetAttr, targetAttr,

View File

@ -88,7 +88,7 @@ export class CoreFormatTextDirective implements OnInit {
extContent.componentId = this.componentId; extContent.componentId = this.componentId;
extContent.siteId = this.siteId; extContent.siteId = this.siteId;
extContent.ngOnInit(); extContent.ngAfterViewInit();
} }
/** /**

View File

@ -1,5 +1,6 @@
{ {
"accounts": "Accounts", "accounts": "Accounts",
"add": "Add",
"allparticipants": "All participants", "allparticipants": "All participants",
"android": "Android", "android": "Android",
"areyousure": "Are you sure?", "areyousure": "Are you sure?",

View File

@ -1435,7 +1435,14 @@ export class CoreFilepoolProvider {
protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, mode = 'url', protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, mode = 'url',
timemodified = 0, checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise<string> { timemodified = 0, checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise<string> {
let fileId, let fileId,
revision; revision,
addToQueue = (fileUrl) => {
// Add the file to queue if needed and ignore errors.
this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize,
downloadUnknown, options).catch(() => {
// Ignore errors.
});
};
return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => {
fileUrl = fixedUrl; fileUrl = fixedUrl;
@ -1447,14 +1454,12 @@ export class CoreFilepoolProvider {
if (typeof entry === 'undefined') { if (typeof entry === 'undefined') {
// We do not have the file, add it to the queue, and return real URL. // We do not have the file, add it to the queue, and return real URL.
this.addToQueueIfNeeded( addToQueue(fileUrl);
siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options);
response = fileUrl; response = fileUrl;
} else if (this.isFileOutdated(entry, revision, timemodified) && this.appProvider.isOnline()) { } else if (this.isFileOutdated(entry, revision, timemodified) && this.appProvider.isOnline()) {
// The file is outdated, we add to the queue and return real URL. // The file is outdated, we add to the queue and return real URL.
this.addToQueueIfNeeded( addToQueue(fileUrl);
siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options);
response = fileUrl; response = fileUrl;
} else { } else {
// We found the file entry, now look for the file on disk. // We found the file entry, now look for the file on disk.
@ -1471,8 +1476,7 @@ export class CoreFilepoolProvider {
// We could not retrieve the file, delete the entries associated with that ID. // We could not retrieve the file, delete the entries associated with that ID.
this.logger.debug('File ' + fileId + ' not found on disk'); this.logger.debug('File ' + fileId + ' not found on disk');
this.removeFileById(siteId, fileId); this.removeFileById(siteId, fileId);
this.addToQueueIfNeeded( addToQueue(fileUrl);
siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options);
if (this.appProvider.isOnline()) { if (this.appProvider.isOnline()) {
// We still have a chance to serve the right content. // We still have a chance to serve the right content.
@ -1486,8 +1490,7 @@ export class CoreFilepoolProvider {
return response; return response;
}, () => { }, () => {
// We do not have the file in store yet. Add to queue and return the fixed URL. // We do not have the file in store yet. Add to queue and return the fixed URL.
this.addToQueueIfNeeded( addToQueue(fileUrl);
siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options);
return fileUrl; return fileUrl;
}); });
}); });

View File

@ -47,6 +47,7 @@ export interface CoreSiteBasicInfo {
fullName: string; fullName: string;
siteName: string; siteName: string;
avatar: string; avatar: string;
badge?: number;
}; };
/* /*
@ -750,17 +751,23 @@ export class CoreSitesProvider {
* @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites. * @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites.
* @return {Promise<CoreSiteBasicInfo[]>} Promise resolved when the sites are retrieved. * @return {Promise<CoreSiteBasicInfo[]>} Promise resolved when the sites are retrieved.
*/ */
getSites(ids: string[]) : Promise<CoreSiteBasicInfo[]> { getSites(ids?: string[]) : Promise<CoreSiteBasicInfo[]> {
return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => {
let formattedSites = []; let formattedSites = [];
sites.forEach((site) => { sites.forEach((site) => {
if (!ids || ids.indexOf(site.id) > -1) { if (!ids || ids.indexOf(site.id) > -1) {
// Try to parse info.
let siteInfo = site.info;
try {
siteInfo = siteInfo ? JSON.parse(siteInfo) : siteInfo;
} catch(ex) {}
const basicInfo: CoreSiteBasicInfo = { const basicInfo: CoreSiteBasicInfo = {
id: site.id, id: site.id,
siteUrl: site.siteUrl, siteUrl: site.siteUrl,
fullName: site.info.fullname, fullName: siteInfo && siteInfo.fullname,
siteName: site.info.sitename, siteName: siteInfo && siteInfo.sitename,
avatar: site.info.userpictureurl avatar: siteInfo && siteInfo.userpictureurl
}; };
formattedSites.push(basicInfo); formattedSites.push(basicInfo);
} }