MOBILE-2253 login: Implement sites page
parent
847359bb2a
commit
acdf6b3043
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-core-login-sites {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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?",
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue