Commit 07f02e77 authored by Laurent's avatar Laurent

Merge branch 'master' into toulouse

# Conflicts:
#	MyBibApp/app/models/portal/adapter.ts
parents a2f83dd0 387f0698
......@@ -100,6 +100,7 @@ Label {
}
.loan-item .title,
#novelties .title {
font-weight: bold;
}
......@@ -127,6 +128,12 @@ Label {
}
.loan-item {
text-align: center;
padding: 20dip;
}
/* forms */
.form_label { font-weight: bold; }
.error { color: red; padding:0; height: auto;}
......
......@@ -2,8 +2,9 @@
import 'nativescript-i18n';
import * as app from 'tns-core-modules/application';
import {Database,Manager} from './models';
Database.open('prod');
import {Database, Manager, NSHTTPClient} from './models';
import * as config from "./config/config";
import { AccountsController } from './controllers/accounts';
import * as Frames from 'ui/frame';
function myReloadPage() {
......@@ -27,10 +28,9 @@ function myReloadPage() {
}
global.__onLiveSyncCore = myReloadPage;
import * as config from "./config/config";
Manager.url(config.MANAGER_URL);
import { AccountsController } from './controllers/accounts';
Database.open('prod');
let manager = new Manager(config.MANAGER_URL, new NSHTTPClient());
app.start({ moduleName: 'views/accounts/list',
bindingContext: new AccountsController()});
bindingContext: new AccountsController( manager ) });
......@@ -11,9 +11,11 @@ export class AccountsController extends Controller {
protected _portal: Portal = new Portal();
protected _portals: Array<MBPortal>;
protected _accounts: ObservableArray<Account>;
protected _manager: Manager;
public constructor() {
public constructor(manager: Manager) {
super();
this._manager = manager;
this._accounts = new ObservableArray<Account>();
this._refreshAccounts();
this.set('accounts', this._accounts);
......@@ -48,7 +50,7 @@ export class AccountsController extends Controller {
this.set('currently_edited', account);
this._saveAccount();
let library_controller = new LibraryController(account);
let library_controller = new LibraryController(account, this._manager);
library_controller.doRefreshLoansAndHolds();
this.navigate({
......@@ -109,7 +111,7 @@ export class AccountsController extends Controller {
this._portals = new Array<MBPortal>();
let portals_cache = this._portals;
new Manager().portals().then( (portals) => {
this._manager.portals().then( (portals) => {
portals.map( (element) => { portals_cache.push(element); });
});
......@@ -133,7 +135,7 @@ export class AccountsController extends Controller {
public openAccountAction(args) {
let account = this._accounts.getItem(args.index);
this.navigate({moduleName: "views/library/items",
bindingContext: new LibraryController(account)});
bindingContext: new LibraryController(account, this._manager)});
}
......
......@@ -5,6 +5,7 @@ import { AccountsController } from '../controllers/accounts';
import { Database,
ItemOperation,
Item,
Loan,
Account,
Hold,
......@@ -13,7 +14,8 @@ import { Database,
OAuthTokenNotSet,
WrongLoginPassword,
Cookies,
Manager
Manager,
Iso8601Date
} from '../models/';
import { RadSideDrawer } from 'nativescript-ui-sidedrawer';
......@@ -29,6 +31,7 @@ export class LibraryController extends Controller {
protected _holds: ObservableArray<Hold> = new ObservableArray<Hold>();
protected _on_loans_page_loaded = null;
protected _on_successful_login = null;
protected _manager: Manager;
protected _view;
public setPortal(portal: Portal): LibraryController {
......@@ -37,11 +40,12 @@ export class LibraryController extends Controller {
}
public constructor(account: Account) {
public constructor(account: Account, manager: Manager) {
super();
this._account = account;
this._account = account;
this._portal = new Portal();
this._manager = manager;
this._loadLoansAndHolds();
this.set('loans', this._loans);
this.set('holds', this._holds);
......@@ -51,6 +55,14 @@ export class LibraryController extends Controller {
this.set('show_novelties', this._portal.canFetchNovelties(this._account));
this.set('busy', 'off');
this.set('card', this._account.getCard());
this.set('sip_loan', false);
this._manager
.connect(Database.current(), this._account)
.then( (portal) => {
this.set('sip_loan', portal.sip_loan_feature);
});
}
......@@ -66,7 +78,7 @@ export class LibraryController extends Controller {
public goHomeAction() {
this.navigate({ moduleName: "views/accounts/list",
bindingContext: new AccountsController(),
bindingContext: new AccountsController(this._manager),
clearHistory: true });
}
......@@ -106,7 +118,7 @@ export class LibraryController extends Controller {
public editAccountAction() {
(new AccountsController()).editAccount(this._account);
new AccountsController(this._manager).editAccount(this._account);
}
......@@ -136,6 +148,76 @@ export class LibraryController extends Controller {
}
public loanItemAction() {
this._closeDrawer();
(new BarcodeScanner())
.scan({
formats: "CODE_39",
cancelLabel: L('barcode_scanner_cancel'),
message: L('barcode_scanner_hint'),
showFlipCameraButton: false,
preferFrontCamera: false,
showTorchButton: true,
orientation: "portrait",
openSettingsIfPermissionWasPreviouslyDenied: true
})
.then(
(result) => {
return result.text;
})
.then( (barcode) => {
this._turnBusyIndicator('on');
return this._portal.fetchItem(this._account, barcode);
})
.then( (item: Item) => {
console.dir(item);
this._turnBusyIndicator('off');
this.set('item', item);
this.navigate({ moduleName: "views/library/loan_item",
backstackVisible: false,
bindingContext: this });
});
}
public confirmLoanItemAction() {
this.dialogs
.prompt({
title: L('do_loan'),
message: L('enter_password'),
okButtonText: L('do_loan'),
cancelButtonText: L('cancel'),
inputType: this.dialogs.inputType.password
})
.then( (r) => {
if (!r.result) return;
this._manager
.loan(Database.current(),
this._account,
r.text,
this.get('item').getBarcode())
.then( (result) => {
if (result['success'])
return this.dialogs.alert({title: L('confirm'),
message: L('loan_title_date_due',
result['title'],
new Iso8601Date(result['due_date']).asLongDate()),
okButtonText: L('OK')});
return this.dialogs.confirm( { title: L('error'),
message: result['message'],
okButtonText: L('OK')});
})
})
.then( () => {
this.goBack();
});
}
public scanCardAction() {
(new BarcodeScanner()).scan({
formats: "CODE_39",
......@@ -212,9 +294,6 @@ export class LibraryController extends Controller {
this
._loadLoansAndHolds()
._turnBusyIndicator('off');
this._view.getViewById("loans").notifyPullToRefreshFinished();
this._view.getViewById("holds").notifyPullToRefreshFinished();
})
.then( () => {
......@@ -262,12 +341,12 @@ export class LibraryController extends Controller {
this.navigate(
{ moduleName: "views/accounts/list",
bindingContext: (new AccountsController()).showAddAccountHint(),
bindingContext: (new AccountsController(this._manager)).showAddAccountHint(),
clearHistory: true });
}
protected _authenticationDone(account): LibraryController {
protected _authenticationDone(account:Account): LibraryController {
Database.current().save(account);
this.set('busy', true);
......@@ -358,7 +437,7 @@ export class LibraryController extends Controller {
}
protected _askLoginPassword(): LibraryController {
protected _askLoginPassword(): this {
this.set('credentials', this._account.credentials);
this.navigate({ moduleName: "views/library/login-password",
bindingContext: this,
......@@ -374,7 +453,7 @@ export class LibraryController extends Controller {
}
protected _closeDrawer(): LibraryController {
protected _closeDrawer(): this {
let sideDrawer: RadSideDrawer = <RadSideDrawer>(this._view.getViewById('sideDrawer'));
sideDrawer.closeDrawer();
return this;
......@@ -404,7 +483,10 @@ export class LibraryController extends Controller {
if (!this._view)
return this;
this._view.getViewById("loans").notifyPullToRefreshFinished();
this._view.getViewById("holds").notifyPullToRefreshFinished();
let indicator = this._view.getViewById("activity-indicator");
if (!indicator || (undefined == indicator.animate))
return this;
......
......@@ -60,4 +60,8 @@
<string name="no_holds">No holds. Pull to refresh.</string>
<string name="loans_and_holds">Loans / Holds</string>
<string name="library">Library</string>
<string name="do_loan">Loan item</string>
<string name="do_loan_barcode">Loan : %s</string>
<string name="enter_password">To finish the request, enter your password</string>
<string name="loan_title_date_due">%s, bring back for %s</string>
</resources>
......@@ -60,4 +60,8 @@
<string name="no_holds">No hay reserva. Dezliza para refrescar.</string>
<string name="loans_and_holds">Prestamos / Reservas</string>
<string name="library">Biblioteca</string>
<string name="do_loan">Pedir prestado</string>
<string name="do_loan_barcode">Prestamo : %s</string>
<string name="enter_password">Para finalizar la solicitud, ingrese su contraseña</string>
<string name="loan_title_date_due">%s, para regresar a más tardar %s</string>
</resources>
......@@ -60,4 +60,8 @@
<string name="no_holds">Aucune réservation. Tirez pour rafraîchir.</string>
<string name="loans_and_holds">Prêts / Réservations</string>
<string name="library">Bibliothèque</string>
<string name="do_loan">Emprunter</string>
<string name="do_loan_barcode">Emprunt : %s</string>
<string name="enter_password">Pour finaliser la demande, veuillez saisir votre mot de passe</string>
<string name="loan_title_date_due">%s, à retourner au plus tard le %s</string>
</resources>
......@@ -15,6 +15,7 @@ export {PortalDetectionFail} from './portal/portal-detection-fail';
export {Cookies} from './cookies/cookies';
export {Manager, MBPortal} from './manager';
export {Record} from './record';
export {Item} from './item';
export {Novelties} from './novelties';
export {Card} from './card';
export {Iso8601Date} from './iso8601_date';
......
import { HTTPClient, NSHTTPClient, Account } from '../models';
import { HTTPClient,
Account,
DataSource } from '../models';
import * as UrlParse from 'url-parse';
import {MBPortal} from './manager/mbportal';
export {MBPortal};
export class Manager {
protected static _url: string;
protected _url: string;
protected _http: any;
protected _configuration: any;
public static url (url: string) {
Manager._url = url;
public constructor(url: string, http_client: HTTPClient) {
this._http = http_client;
this._url = url;
this._configuration = {};
}
public constructor() {
this._http = new NSHTTPClient();
this._configuration = {};
}
public request(service: string, content: Object = undefined): Promise<any> {
let params = { url: this._url + '/api/' + service,
headers: { "Content-Type": "application/json" } };
if (content) {
params['method'] = "POST";
params['content'] = JSON.stringify(content);
}
return this._http
.request(params)
.then( (response) => {
console.dir(response.content.toString());
let datas = response.content.toJSON();
return datas;
})
}
public setHTTP(http: HTTPClient): Manager {
this._http = http;
return this;
public connect(db: DataSource, account:Account): Promise<MBPortal> {
let portal = this._findOrCreatePortal(db, account);
return this
.request('portal/' + portal.getKey())
.then( (datas) => {
portal
.setLabel(datas['label'])
.setURL(datas['url'])
.setFeatures(datas['features']);
db.save(portal);
return portal;
})
.catch( () => {
portal.setFeatures([]);
db.save(portal);
return portal;
});
}
public connect(account:Account, callback:any): Promise<any> {
return this._http
.request({ url: Manager._url + '/api/configuration',
method: "POST",
headers: { "Content-Type": "application/json" },
content: JSON.stringify( { url: account.getUrl() } )
})
.then( (response) => {
let datas = response.content.toJSON();
callback(datas);
});
protected _findOrCreatePortal(db: DataSource, account: Account): MBPortal {
let portal:MBPortal = MBPortal.findByURL(db, account.getUrl());
return portal == undefined
? new MBPortal().setURL(account.getUrl())
: portal;
}
public portals(): Promise<any> {
return this._http
.request({ url: Manager._url + '/api/portals' })
.then( (response) => {
let datas = response.content.toJSON();
public portals(): Promise<Array<MBPortal>> {
return this
.request('portals')
.then( (datas) => {
let portals = datas.map( (portal) => {
return new MBPortal(portal.label, portal.url);
return new MBPortal()
.setLabel(portal.label)
.setURL(portal.url);
});
return Promise.resolve(portals);
});
}
public loan(db: DataSource, account: Account, password: string, barcode: string): Promise<Object> {
let portal = this._findOrCreatePortal(db, account);
return this
.request('loan/' + portal.getKey(),
{ login: account.credentials['login'],
password: password,
barcode: barcode
})
}
}
export class MBPortal {
protected _label: string;
protected _url: string;
import {Persistable,
Serializable,
DataSource} from '../';
import * as UrlParse from 'url-parse';
export class MBPortal extends Persistable {
protected _label: string = '';
protected _url: string = '';
protected _features: Array<Object> = new Array<Object>();
public static findByURL(db:DataSource, url:string): MBPortal {
return db.findAll(MBPortal).find( (portal:MBPortal) => {
return portal.keyMatches(url);
});
}
public constructor(label:string, url:string) {
public getLabel(): string {
return this._label;
}
public getKey(): string {
return this._hostname(this._url);
}
protected _hostname(url: string): string {
return (new UrlParse(url)).host;
}
public keyMatches(url: string): boolean {
return this.getKey() == this._hostname(url);
}
public setLabel(label:string): this {
this._label = label;
return this;
}
public setURL(url:string): this {
this._url = url;
return this;
}
public getURL(): string {
return this._url;
}
public setFeatures(features:Array<Object>): this {
this._features = features;
return this;
}
get label(): string {
return this._label;
}
get sip_loan_feature(): boolean {
return undefined != this._features.find( (feature) => {
return feature['code'] == 'SIP_LOAN';
});
}
get url(): string {
return this._url;
}
public serializeOn(serializable: Serializable) {
serializable
.set('label', this._label)
.set('url', this._url)
}
public materializeFrom(serializable: Serializable) {
this._label = serializable.get('label');
this._url = serializable.get('url');
}
}
import * as PortalAdapters from './portal/index';
import {PortalAdapter} from './portal/adapter';
import {Database, Account} from '../models';
import {Database, Account, Item} from '../models';
export class Portal {
protected _adapters: Map<string, PortalAdapter> = new Map<string, PortalAdapter>();
......@@ -52,7 +52,14 @@ export class Portal {
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.fetchNovelties(account, page);
}
public fetchItem(account: Account, barcode: string): Promise<Item> {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.fetchItem(account, barcode);
}
public canSearch(account: Account): boolean {
return this
......
......@@ -4,6 +4,7 @@ import {Cookies,
Account,
Database,
Loan,
Item,
Hold} from '../../models';
export abstract class PortalAdapter {
......@@ -127,6 +128,11 @@ export abstract class PortalAdapter {
}
public fetchItem(account, barcode): Promise<Item> {
return Promise.reject(null);
}
protected _fetchAccountLabel(account: Account): Promise<any> {
return Promise.resolve(account.credentials['login']);
};
......@@ -145,7 +151,6 @@ export abstract class PortalAdapter {
return datas.join('&');
}
protected abstract _signIn(account: Account): Promise<any>;
public abstract getIdentifier(): string;
......
import {PortalAdapter} from './adapter';
import {Database, Account, Hold, Loan, Record} from '../../models';
import {Database, Account, Hold, Loan, Record, Item} from '../../models';
import {OAuthTokenNotSet} from './oauth-token-not-set';
......@@ -84,6 +84,28 @@ export class Bokeh extends PortalAdapter {
}
public fetchItem(account, barcode): Promise<Item> {
return this
.request(account,
{ url: account.getUrl() + '/api/catalog/item/barcode/' + barcode })
.then( (response) => {
let datas = response.content.toJSON();
let item = new Item(datas['barcode'])
item
.setTitle(datas['title'])
.setAuthor(datas['author']);
let record = datas['record'];
if (!record)
return item;
return item
.setRecordId(record['id'])
.setThumbnailUrl(record['thumbnail']);
})
}
public searchUrl(account: Account, terms: string): Promise<string> {
return Promise.resolve(account.getUrl() + '/recherche/simple/expressionRecherche/' + terms);
}
......@@ -156,6 +178,7 @@ export class Bokeh extends PortalAdapter {
}
protected _signIn(account): Promise<any> {
if (!account.credentials['token'])
......
export class Record {
protected _url: string;
protected _author: string;
protected _title: string;
protected _doc_type: string;
protected _thumbnail_url: string;
protected _id: string = '';
protected _url: string = '';
protected _author: string = '';
protected _title: string = '';
protected _doc_type: string = '';
protected _thumbnail_url: string = '';
public setId(id: string): this {
this._id = id;
return this;
}
public setUrl(url:string): Record {
public setUrl(url:string): this {
this._url = url;
return this;
}
public setDocType(doc_type: string): Record {
public setDocType(doc_type: string): this {
this._doc_type = doc_type;
return this;
}
public setThumbnailUrl(url: string): Record {
public setThumbnailUrl(url: string): this {
this._thumbnail_url = url;
return this;
}
public setTitle(title: string): Record {
public setTitle(title: string): this {
this._title = title;
return this;
}
public setAuthor(author: string): Record {
public setAuthor(author: string): this {
this._author = author;
return this;
}
......
declare var describe, expect, it, before, chai: any;
import {AccountsController} from '../controllers/accounts';
import {LibraryController} from '../controllers/library';
import {Account, Database} from '../models/';
import {Account, Database, Manager, NullHTTPClient} from '../models/';
import {StubFrames} from './stub-frames';
describe('AccountsController with one account in database', () => {
......@@ -10,14 +10,16 @@ describe('AccountsController with one account in database', () => {
let navigate_spy;
let goback_spy;
let bibliofil: Account;
let manager: Manager;
before( () => {
bibliofil = (new Account()).setLabel('Bibliofil');