mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
chore(build): rename ionic directory to src and update all references in the build process.
This commit is contained in:
25
src/platform/cordova.ios.scss
Normal file
25
src/platform/cordova.ios.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
@import "../globals.ios";
|
||||
@import "./cordova";
|
||||
|
||||
// iOS Cordova
|
||||
// --------------------------------------------------
|
||||
|
||||
$cordova-ios-statusbar-padding-modal-max-width: $cordova-statusbar-padding-modal-max-width !default;
|
||||
|
||||
|
||||
@include navbar-section-statusbar-padding($toolbar-ios-height);
|
||||
@include navbar-section-title-statusbar-padding($toolbar-ios-height);
|
||||
|
||||
.tab-subpage,
|
||||
ion-nav > ion-page,
|
||||
ion-menu {
|
||||
@include toolbar-statusbar-padding($toolbar-ios-height, $content-ios-padding);
|
||||
@include toolbar-title-statusbar-padding($toolbar-ios-height, $content-ios-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $cordova-ios-statusbar-padding-modal-max-width) {
|
||||
.modal-wrapper > ion-page {
|
||||
@include toolbar-statusbar-padding($toolbar-ios-height, $content-ios-padding);
|
||||
@include toolbar-title-statusbar-padding($toolbar-ios-height, $content-ios-padding);
|
||||
}
|
||||
}
|
||||
22
src/platform/cordova.md.scss
Normal file
22
src/platform/cordova.md.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
@import "../globals.md";
|
||||
@import "./cordova";
|
||||
|
||||
// Material Design on iOS with Cordova
|
||||
// --------------------------------------------------
|
||||
|
||||
$cordova-md-statusbar-padding-modal-max-width: $cordova-statusbar-padding-modal-max-width !default;
|
||||
|
||||
|
||||
@include navbar-section-statusbar-padding($toolbar-md-height);
|
||||
|
||||
.tab-subpage,
|
||||
ion-nav > ion-page,
|
||||
ion-menu {
|
||||
@include toolbar-statusbar-padding($toolbar-md-height, $content-md-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $cordova-md-statusbar-padding-modal-max-width) {
|
||||
.modal-wrapper > ion-page {
|
||||
@include toolbar-statusbar-padding($toolbar-md-height, $content-md-padding);
|
||||
}
|
||||
}
|
||||
81
src/platform/cordova.scss
Normal file
81
src/platform/cordova.scss
Normal file
@@ -0,0 +1,81 @@
|
||||
@import "../globals.core";
|
||||
|
||||
// Cordova Status Bar Padding iOS Platform
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// The height of the ios statusbar
|
||||
$cordova-ios-statusbar-padding: 20px !default;
|
||||
|
||||
// The breakpoint when a modal becomes inset
|
||||
$cordova-statusbar-padding-modal-max-width: 767px !default;
|
||||
|
||||
|
||||
// The first-child should get modified padding-top for the status bar
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
@mixin toolbar-statusbar-padding($toolbar-height, $content-padding) {
|
||||
|
||||
> .toolbar.statusbar-padding:first-child {
|
||||
padding-top: $cordova-ios-statusbar-padding;
|
||||
|
||||
height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
min-height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
> ion-content.statusbar-padding:first-child scroll-content {
|
||||
padding-top: $cordova-ios-statusbar-padding;
|
||||
}
|
||||
|
||||
> ion-content.statusbar-padding:first-child[padding] scroll-content,
|
||||
> ion-content.statusbar-padding:first-child[padding-top] scroll-content {
|
||||
padding-top: calc(#{$content-padding} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The navbar section should get modified height and padding-top for the status bar
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
@mixin navbar-section-statusbar-padding($toolbar-height) {
|
||||
|
||||
ion-navbar-section.statusbar-padding {
|
||||
height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
min-height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
ion-navbar-section.statusbar-padding > .toolbar {
|
||||
padding-top: $cordova-ios-statusbar-padding;
|
||||
|
||||
height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
min-height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// iOS is the only mode that uses these mixins and they should be removed with #5036
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
@mixin toolbar-title-statusbar-padding($toolbar-height, $content-padding) {
|
||||
|
||||
> .toolbar.statusbar-padding:first-child ion-segment,
|
||||
> .toolbar.statusbar-padding:first-child ion-title {
|
||||
padding-top: $cordova-ios-statusbar-padding;
|
||||
|
||||
height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
min-height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@mixin navbar-section-title-statusbar-padding($toolbar-height) {
|
||||
|
||||
ion-navbar-section.statusbar-padding > .toolbar ion-segment,
|
||||
ion-navbar-section.statusbar-padding > .toolbar ion-title {
|
||||
padding-top: $cordova-ios-statusbar-padding;
|
||||
|
||||
height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
min-height: calc(#{$toolbar-height} + #{$cordova-ios-statusbar-padding});
|
||||
}
|
||||
|
||||
}
|
||||
22
src/platform/cordova.wp.scss
Normal file
22
src/platform/cordova.wp.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
@import "../globals.wp";
|
||||
@import "./cordova";
|
||||
|
||||
// Windows on iOS with Cordova
|
||||
// --------------------------------------------------
|
||||
|
||||
$cordova-wp-statusbar-padding-modal-max-width: $cordova-statusbar-padding-modal-max-width !default;
|
||||
|
||||
|
||||
@include navbar-section-statusbar-padding($toolbar-wp-height);
|
||||
|
||||
.tab-subpage,
|
||||
ion-nav > ion-page,
|
||||
ion-menu {
|
||||
@include toolbar-statusbar-padding($toolbar-wp-height, $content-wp-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $cordova-wp-statusbar-padding-modal-max-width) {
|
||||
.modal-wrapper > ion-page {
|
||||
@include toolbar-statusbar-padding($toolbar-wp-height, $content-wp-padding);
|
||||
}
|
||||
}
|
||||
785
src/platform/platform.ts
Normal file
785
src/platform/platform.ts
Normal file
@@ -0,0 +1,785 @@
|
||||
import {EventEmitter, NgZone} from '@angular/core';
|
||||
|
||||
import {Config} from '../config/config';
|
||||
import {getQuerystring} from '../util/util';
|
||||
import {ready, windowDimensions, flushDimensionCache} from '../util/dom';
|
||||
|
||||
/**
|
||||
* @name Platform
|
||||
* @description
|
||||
* The Platform service can be used to get information about your current device.
|
||||
* You can get all of the platforms associated with the device using the [platforms](#platforms)
|
||||
* method, including whether the app is being viewed from a tablet, if it's
|
||||
* on a mobile device or browser, and the exact platform (iOS, Android, etc).
|
||||
* You can also get the orientation of the device, if it uses right-to-left
|
||||
* language direction, and much much more. With this information you can completely
|
||||
* customize your app to fit any device.
|
||||
*
|
||||
* @usage
|
||||
* ```ts
|
||||
* import {Platform} from 'ionic-angular';
|
||||
*
|
||||
* @Page({...})
|
||||
* export MyPage {
|
||||
* constructor(platform: Platform) {
|
||||
* this.platform = platform;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @demo /docs/v2/demos/platform/
|
||||
*/
|
||||
export class Platform {
|
||||
private _platforms: Array<string>;
|
||||
private _versions: {[name: string]: PlatformVersion} = {};
|
||||
private _dir: string;
|
||||
private _lang: string;
|
||||
private _url: string;
|
||||
private _qs: any;
|
||||
private _ua: string;
|
||||
private _bPlt: string;
|
||||
private _onResizes: Array<Function> = [];
|
||||
private _readyPromise: Promise<any>;
|
||||
private _readyResolve: any;
|
||||
private _resizeTm: any;
|
||||
private _zone: NgZone;
|
||||
|
||||
constructor(platforms = []) {
|
||||
this._platforms = platforms;
|
||||
this._readyPromise = new Promise(res => { this._readyResolve = res; } );
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setZone(zone: NgZone) {
|
||||
this._zone = zone;
|
||||
}
|
||||
|
||||
|
||||
// Methods
|
||||
// **********************************************
|
||||
|
||||
/**
|
||||
* @returns {boolean} returns true/false based on platform.
|
||||
* @description
|
||||
* Depending on the platform the user is on, `is(platformName)` will
|
||||
* return `true` or `false`. Note that the same app can return `true`
|
||||
* for more than one platform name. For example, an app running from
|
||||
* an iPad would return `true` for the platform names: `mobile`,
|
||||
* `ios`, `ipad`, and `tablet`. Additionally, if the app was running
|
||||
* from Cordova then `cordova` would be true, and if it was running
|
||||
* from a web browser on the iPad then `mobileweb` would be `true`.
|
||||
*
|
||||
* ```
|
||||
* import {Platform} from 'ionic-angular';
|
||||
*
|
||||
* @Page({...})
|
||||
* export MyPage {
|
||||
* constructor(platform: Platform) {
|
||||
* this.platform = platform;
|
||||
*
|
||||
* if (this.platform.is('ios')) {
|
||||
* // This will only print when on iOS
|
||||
* console.log("I'm an iOS device!");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* | Platform Name | Description |
|
||||
* |-----------------|------------------------------------|
|
||||
* | android | on a device running Android. |
|
||||
* | cordova | on a device running Cordova. |
|
||||
* | core | on a desktop device. |
|
||||
* | ios | on a device running iOS. |
|
||||
* | ipad | on an iPad device. |
|
||||
* | iphone | on an iPhone device. |
|
||||
* | mobile | on a mobile device. |
|
||||
* | mobileweb | in a browser on a mobile device. |
|
||||
* | phablet | on a phablet device. |
|
||||
* | tablet | on a tablet device. |
|
||||
* | windows | on a device running Windows. |
|
||||
*
|
||||
* @param {string} platformName
|
||||
*/
|
||||
is(platformName: string): boolean {
|
||||
return (this._platforms.indexOf(platformName) > -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {array} the array of platforms
|
||||
* @description
|
||||
* Depending on what device you are on, `platforms` can return multiple values.
|
||||
* Each possible value is a hierarchy of platforms. For example, on an iPhone,
|
||||
* it would return `mobile`, `ios`, and `iphone`.
|
||||
*
|
||||
* ```
|
||||
* import {Platform} from 'ionic-angular';
|
||||
*
|
||||
* @Page({...})
|
||||
* export MyPage {
|
||||
* constructor(platform: Platform) {
|
||||
* this.platform = platform;
|
||||
*
|
||||
* // This will print an array of the current platforms
|
||||
* console.log(this.platform.platforms());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
platforms(): Array<string> {
|
||||
// get the array of active platforms, which also knows the hierarchy,
|
||||
// with the last one the most important
|
||||
return this._platforms;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object containing version information about all of the platforms.
|
||||
*
|
||||
* ```
|
||||
* import {Platform} from 'ionic-angular';
|
||||
*
|
||||
* @Page({...})
|
||||
* export MyPage {
|
||||
* constructor(platform: Platform) {
|
||||
* this.platform = platform;
|
||||
*
|
||||
* // This will print an object containing
|
||||
* // all of the platforms and their versions
|
||||
* console.log(platform.versions());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns {object} An object containing all of the platforms and their versions.
|
||||
*/
|
||||
versions(): {[name: string]: PlatformVersion} {
|
||||
// get all the platforms that have a valid parsed version
|
||||
return this._versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
version(): PlatformVersion {
|
||||
for (let platformName in this._versions) {
|
||||
if (this._versions[platformName]) {
|
||||
return this._versions[platformName];
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise when the platform is ready and native functionality
|
||||
* can be called. If the app is running from within a web browser, then
|
||||
* the promise will resolve when the DOM is ready. When the app is running
|
||||
* from an application engine such as Cordova, then the promise will
|
||||
* resolve when Cordova triggers the `deviceready` event.
|
||||
*
|
||||
* The resolved value is the `readySource`, which states which platform
|
||||
* ready was used. For example, when Cordova is ready, the resolved ready
|
||||
* source is `cordova`. The default ready source value will be `dom`. The
|
||||
* `readySource` is useful if different logic should run depending on the
|
||||
* platform the app is running from. For example, only Cordova can execute
|
||||
* the status bar plugin, so the web should not run status bar plugin logic.
|
||||
*
|
||||
* ```
|
||||
* import {App, Platform} from 'ionic-angular';
|
||||
*
|
||||
* @App({...})
|
||||
* export MyApp {
|
||||
* constructor(platform: Platform) {
|
||||
* platform.ready().then((readySource) => {
|
||||
* console.log('Platform ready from', readySource);
|
||||
* // Platform now ready, execute any required native code
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @returns {promise}
|
||||
*/
|
||||
ready(): Promise<string> {
|
||||
return this._readyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* This should be triggered by the engine when the platform is
|
||||
* ready. If there was no custom prepareReady method from the engine,
|
||||
* such as Cordova or Electron, then it uses the default DOM ready.
|
||||
*/
|
||||
triggerReady(readySource: string) {
|
||||
this._zone.run(() => {
|
||||
this._readyResolve(readySource);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* This is the default prepareReady if it's not replaced by an engine,
|
||||
* such as Cordova or Electron. If there was no custom prepareReady
|
||||
* method from an engine then it uses the method below, which triggers
|
||||
* the platform ready on the DOM ready event, and the default resolved
|
||||
* value is `dom`.
|
||||
*/
|
||||
prepareReady() {
|
||||
ready(() => {
|
||||
this.triggerReady('dom');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the app's language direction, which will update the `dir` attribute
|
||||
* on the app's root `<html>` element. We recommend the app's `index.html`
|
||||
* file already has the correct `dir` attribute value set, such as
|
||||
* `<html dir="ltr">` or `<html dir="rtl">`. This method is useful if the
|
||||
* direction needs to be dynamically changed per user/session.
|
||||
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
|
||||
* @param {string} dir Examples: `rtl`, `ltr`
|
||||
*/
|
||||
setDir(dir: string, updateDocument: boolean) {
|
||||
this._dir = (dir || '').toLowerCase();
|
||||
if (updateDocument !== false) {
|
||||
document.documentElement.setAttribute('dir', dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns app's language direction.
|
||||
* We recommend the app's `index.html` file already has the correct `dir`
|
||||
* attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
|
||||
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
|
||||
* @returns {string}
|
||||
*/
|
||||
dir(): string {
|
||||
return this._dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this app is using right-to-left language direction or not.
|
||||
* We recommend the app's `index.html` file already has the correct `dir`
|
||||
* attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
|
||||
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isRTL(): boolean {
|
||||
return (this._dir === 'rtl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the app's language and optionally the country code, which will update
|
||||
* the `lang` attribute on the app's root `<html>` element.
|
||||
* We recommend the app's `index.html` file already has the correct `lang`
|
||||
* attribute value set, such as `<html lang="en">`. This method is useful if
|
||||
* the language needs to be dynamically changed per user/session.
|
||||
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
|
||||
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
|
||||
*/
|
||||
setLang(language: string, updateDocument: boolean) {
|
||||
this._lang = language;
|
||||
if (updateDocument !== false) {
|
||||
document.documentElement.setAttribute('lang', language);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns app's language and optional country code.
|
||||
* We recommend the app's `index.html` file already has the correct `lang`
|
||||
* attribute value set, such as `<html lang="en">`.
|
||||
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
|
||||
* @returns {string}
|
||||
*/
|
||||
lang(): string {
|
||||
return this._lang;
|
||||
}
|
||||
|
||||
// Methods meant to be overridden by the engine
|
||||
// **********************************************
|
||||
// Provided NOOP methods so they do not error when
|
||||
// called by engines (the browser)that do not provide them
|
||||
|
||||
/**
|
||||
* The `exitApp` method is useful when running from a native platform,
|
||||
* such as Cordova. This adds the ability to place the Cordova app
|
||||
* in the background.
|
||||
*/
|
||||
exitApp() {}
|
||||
|
||||
// Events meant to be triggered by the engine
|
||||
// **********************************************
|
||||
|
||||
/**
|
||||
* The back button event is emitted when the user presses the native
|
||||
* platform's back button, also referred to as the "hardware" back button.
|
||||
* This event is only emitted within Cordova apps running on Android and
|
||||
* Windows platforms. This event is not fired on iOS since iOS doesn't come
|
||||
* with a hardware back button in the same sense an Android or Windows device
|
||||
* does. It's important to note that this event does not emit when the Ionic
|
||||
* app's back button within the navbar is clicked, but this event is only
|
||||
* referencing the platform's hardward back button.
|
||||
*/
|
||||
backButton: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* The pause event emits when the native platform puts the application
|
||||
* into the background, typically when the user switches to a different
|
||||
* application. This event would emit when a Cordova app is put into
|
||||
* the background, however, it would not fire on a standard web browser.
|
||||
*/
|
||||
pause: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* The resume event emits when the native platform pulls the application
|
||||
* out from the background. This event would emit when a Cordova app comes
|
||||
* out from the background, however, it would not fire on a standard web browser.
|
||||
*/
|
||||
resume: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
|
||||
// Getter/Setter Methods
|
||||
// **********************************************
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setUrl(url: string) {
|
||||
this._url = url;
|
||||
this._qs = getQuerystring(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
url(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
query(key: string): string {
|
||||
return (this._qs || {})[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setUserAgent(userAgent: string) {
|
||||
this._ua = userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
userAgent(): string {
|
||||
return this._ua || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setNavigatorPlatform(navigatorPlatform: string) {
|
||||
this._bPlt = navigatorPlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navigatorPlatform(): string {
|
||||
return this._bPlt || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
width(): number {
|
||||
return windowDimensions().width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
height(): number {
|
||||
return windowDimensions().height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isPortrait(): boolean {
|
||||
return this.width() < this.height();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isLandscape(): boolean {
|
||||
return !this.isPortrait();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
windowResize() {
|
||||
let self = this;
|
||||
clearTimeout(self._resizeTm);
|
||||
|
||||
self._resizeTm = setTimeout(() => {
|
||||
flushDimensionCache();
|
||||
|
||||
for (let i = 0; i < self._onResizes.length; i++) {
|
||||
try {
|
||||
self._onResizes[i]();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns Unregister function
|
||||
*/
|
||||
onResize(cb: Function): Function {
|
||||
let self = this;
|
||||
self._onResizes.push(cb);
|
||||
|
||||
return function() {
|
||||
let index = self._onResizes.indexOf(cb);
|
||||
if (index > -1) {
|
||||
self._onResizes.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Platform Registry
|
||||
// **********************************************
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static register(platformConfig: PlatformConfig) {
|
||||
platformRegistry[platformConfig.name] = platformConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static registry() {
|
||||
return platformRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static get(platformName: string): PlatformConfig {
|
||||
return platformRegistry[platformName] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static setDefault(platformName: string) {
|
||||
platformDefault = platformName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
testQuery(queryValue: string, queryTestValue: string): boolean {
|
||||
let valueSplit = queryValue.toLowerCase().split(';');
|
||||
return valueSplit.indexOf(queryTestValue) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
testNavigatorPlatform(navigatorPlatformExpression: string): boolean {
|
||||
let rgx = new RegExp(navigatorPlatformExpression, 'i');
|
||||
return rgx.test(this._bPlt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
matchUserAgentVersion(userAgentExpression: RegExp): any {
|
||||
if (this._ua && userAgentExpression) {
|
||||
let val = this._ua.match(userAgentExpression);
|
||||
if (val) {
|
||||
return {
|
||||
major: val[1],
|
||||
minor: val[2]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isPlatformMatch(queryStringName: string, userAgentAtLeastHas?: string[], userAgentMustNotHave: string[] = []): boolean {
|
||||
let queryValue = this.query('ionicplatform');
|
||||
if (queryValue) {
|
||||
return this.testQuery(queryValue, queryStringName);
|
||||
}
|
||||
|
||||
userAgentAtLeastHas = userAgentAtLeastHas || [queryStringName];
|
||||
|
||||
let userAgent = this._ua.toLowerCase();
|
||||
|
||||
for (var i = 0; i < userAgentAtLeastHas.length; i++) {
|
||||
if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) {
|
||||
for (var j = 0; j < userAgentMustNotHave.length; j++) {
|
||||
if (userAgent.indexOf(userAgentMustNotHave[j]) > -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
load(config: Config) {
|
||||
let rootPlatformNode: PlatformNode;
|
||||
let enginePlatformNode: PlatformNode;
|
||||
let self = this;
|
||||
|
||||
// figure out the most specific platform and active engine
|
||||
let tmpPlatform: PlatformNode;
|
||||
for (let platformName in platformRegistry) {
|
||||
|
||||
tmpPlatform = this.matchPlatform(platformName);
|
||||
if (tmpPlatform) {
|
||||
// we found a platform match!
|
||||
// check if its more specific than the one we already have
|
||||
|
||||
if (tmpPlatform.isEngine) {
|
||||
// because it matched then this should be the active engine
|
||||
// you cannot have more than one active engine
|
||||
enginePlatformNode = tmpPlatform;
|
||||
|
||||
} else if (!rootPlatformNode || tmpPlatform.depth > rootPlatformNode.depth) {
|
||||
// only find the root node for platforms that are not engines
|
||||
// set this node as the root since we either don't already
|
||||
// have one, or this one is more specific that the current one
|
||||
rootPlatformNode = tmpPlatform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rootPlatformNode) {
|
||||
rootPlatformNode = new PlatformNode(platformDefault);
|
||||
}
|
||||
|
||||
// build a Platform instance filled with the
|
||||
// hierarchy of active platforms and settings
|
||||
|
||||
if (rootPlatformNode) {
|
||||
|
||||
// check if we found an engine node (cordova/node-webkit/etc)
|
||||
if (enginePlatformNode) {
|
||||
// add the engine to the first in the platform hierarchy
|
||||
// the original rootPlatformNode now becomes a child
|
||||
// of the engineNode, which is not the new root
|
||||
enginePlatformNode.child = rootPlatformNode;
|
||||
rootPlatformNode.parent = enginePlatformNode;
|
||||
rootPlatformNode = enginePlatformNode;
|
||||
}
|
||||
|
||||
let platformNode = rootPlatformNode;
|
||||
while (platformNode) {
|
||||
insertSuperset(platformNode);
|
||||
platformNode = platformNode.child;
|
||||
}
|
||||
|
||||
// make sure the root noot is actually the root
|
||||
// incase a node was inserted before the root
|
||||
platformNode = rootPlatformNode.parent;
|
||||
while (platformNode) {
|
||||
rootPlatformNode = platformNode;
|
||||
platformNode = platformNode.parent;
|
||||
}
|
||||
|
||||
platformNode = rootPlatformNode;
|
||||
while (platformNode) {
|
||||
platformNode.initialize(this, config);
|
||||
|
||||
// set the array of active platforms with
|
||||
// the last one in the array the most important
|
||||
this._platforms.push(platformNode.name);
|
||||
|
||||
// get the platforms version if a version parser was provided
|
||||
this._versions[platformNode.name] = platformNode.version(this);
|
||||
|
||||
// go to the next platform child
|
||||
platformNode = platformNode.child;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._platforms.indexOf('mobile') > -1 && this._platforms.indexOf('cordova') === -1) {
|
||||
this._platforms.push('mobileweb');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private matchPlatform(platformName: string): PlatformNode {
|
||||
// build a PlatformNode and assign config data to it
|
||||
// use it's getRoot method to build up its hierarchy
|
||||
// depending on which platforms match
|
||||
let platformNode = new PlatformNode(platformName);
|
||||
let rootNode = platformNode.getRoot(this);
|
||||
|
||||
if (rootNode) {
|
||||
rootNode.depth = 0;
|
||||
let childPlatform = rootNode.child;
|
||||
while (childPlatform) {
|
||||
rootNode.depth++;
|
||||
childPlatform = childPlatform.child;
|
||||
}
|
||||
}
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function insertSuperset(platformNode: PlatformNode) {
|
||||
let supersetPlaformName = platformNode.superset();
|
||||
if (supersetPlaformName) {
|
||||
// add a platform in between two exist platforms
|
||||
// so we can build the correct hierarchy of active platforms
|
||||
let supersetPlatform = new PlatformNode(supersetPlaformName);
|
||||
supersetPlatform.parent = platformNode.parent;
|
||||
supersetPlatform.child = platformNode;
|
||||
if (supersetPlatform.parent) {
|
||||
supersetPlatform.parent.child = supersetPlatform;
|
||||
}
|
||||
platformNode.parent = supersetPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
class PlatformNode {
|
||||
private c: PlatformConfig;
|
||||
|
||||
parent: PlatformNode;
|
||||
child: PlatformNode;
|
||||
name: string;
|
||||
isEngine: boolean;
|
||||
depth: number;
|
||||
|
||||
constructor(platformName: string) {
|
||||
this.c = Platform.get(platformName);
|
||||
this.name = platformName;
|
||||
this.isEngine = this.c.isEngine;
|
||||
}
|
||||
|
||||
settings(): any {
|
||||
return this.c.settings || {};
|
||||
}
|
||||
|
||||
superset(): any {
|
||||
return this.c.superset;
|
||||
}
|
||||
|
||||
isMatch(p: Platform): boolean {
|
||||
return this.c.isMatch && this.c.isMatch(p) || false;
|
||||
}
|
||||
|
||||
initialize(platform: Platform, config: Config) {
|
||||
this.c.initialize && this.c.initialize(platform, config);
|
||||
}
|
||||
|
||||
version(p: Platform): PlatformVersion {
|
||||
if (this.c.versionParser) {
|
||||
let v = this.c.versionParser(p);
|
||||
if (v) {
|
||||
let str = v.major + '.' + v.minor;
|
||||
return {
|
||||
str: str,
|
||||
num: parseFloat(str),
|
||||
major: parseInt(v.major, 10),
|
||||
minor: parseInt(v.minor, 10)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getRoot(p: Platform): PlatformNode {
|
||||
if (this.isMatch(p)) {
|
||||
|
||||
let parents = this.getSubsetParents(this.name);
|
||||
|
||||
if (!parents.length) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let platform = null;
|
||||
let rootPlatform = null;
|
||||
|
||||
for (let i = 0; i < parents.length; i++) {
|
||||
platform = new PlatformNode(parents[i]);
|
||||
platform.child = this;
|
||||
|
||||
rootPlatform = platform.getRoot(p);
|
||||
if (rootPlatform) {
|
||||
this.parent = platform;
|
||||
return rootPlatform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getSubsetParents(subsetPlatformName: string): Array<string> {
|
||||
let platformRegistry = Platform.registry();
|
||||
|
||||
let parentPlatformNames = [];
|
||||
let platform = null;
|
||||
|
||||
for (let platformName in platformRegistry) {
|
||||
platform = platformRegistry[platformName];
|
||||
|
||||
if (platform.subsets && platform.subsets.indexOf(subsetPlatformName) > -1) {
|
||||
parentPlatformNames.push(platformName);
|
||||
}
|
||||
}
|
||||
|
||||
return parentPlatformNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let platformRegistry: {[name: string]: PlatformConfig} = {};
|
||||
let platformDefault: string = null;
|
||||
|
||||
export interface PlatformConfig {
|
||||
name?: string;
|
||||
isEngine?: boolean;
|
||||
initialize?: Function;
|
||||
isMatch?: Function;
|
||||
superset?: string;
|
||||
subsets?: string[];
|
||||
settings?: any;
|
||||
versionParser?: any;
|
||||
}
|
||||
|
||||
export interface PlatformVersion {
|
||||
str?: string;
|
||||
num?: number;
|
||||
major?: number;
|
||||
minor?: number;
|
||||
}
|
||||
213
src/platform/registry.ts
Normal file
213
src/platform/registry.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import {Platform} from './platform';
|
||||
import {Config} from '../config/config';
|
||||
import {windowLoad} from '../util/dom';
|
||||
|
||||
const win: any = window;
|
||||
const doc: any = document;
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'core',
|
||||
settings: {
|
||||
mode: 'md',
|
||||
keyboardHeight: 290,
|
||||
}
|
||||
});
|
||||
Platform.setDefault('core');
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'mobile'
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'phablet',
|
||||
isMatch(p) {
|
||||
let smallest = Math.min(p.width(), p.height());
|
||||
let largest = Math.max(p.width(), p.height());
|
||||
return (smallest > 390 && smallest < 520) &&
|
||||
(largest > 620 && largest < 800);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'tablet',
|
||||
isMatch(p) {
|
||||
let smallest = Math.min(p.width(), p.height());
|
||||
let largest = Math.max(p.width(), p.height());
|
||||
return (smallest > 460 && smallest < 820) &&
|
||||
(largest > 780 && largest < 1400);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'android',
|
||||
superset: 'mobile',
|
||||
subsets: [
|
||||
'phablet',
|
||||
'tablet'
|
||||
],
|
||||
settings: {
|
||||
activator: function(p: Platform): string {
|
||||
// md mode defaults to use ripple activator
|
||||
// however, under-powered devices shouldn't use ripple
|
||||
// if this a linux device, and is using Android Chrome v36 (Android 5.0)
|
||||
// or above then use ripple, otherwise do not use a ripple effect
|
||||
if (p.testNavigatorPlatform('linux')) {
|
||||
let chromeVersion = p.matchUserAgentVersion(/Chrome\/(\d+).(\d+)?/);
|
||||
if (chromeVersion) {
|
||||
// linux android device using modern android chrome browser gets ripple
|
||||
return (parseInt(chromeVersion.major, 10) < 36 ? 'none' : 'ripple');
|
||||
}
|
||||
// linux android device not using chrome browser checks just android's version
|
||||
if (p.version().major < 5) {
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to always use ripple
|
||||
return 'ripple';
|
||||
},
|
||||
autoFocusAssist: 'immediate',
|
||||
hoverCSS: false,
|
||||
keyboardHeight: 300,
|
||||
mode: 'md',
|
||||
},
|
||||
isMatch(p: Platform): boolean {
|
||||
return p.isPlatformMatch('android', ['android', 'silk'], ['windows phone']);
|
||||
},
|
||||
versionParser(p: Platform): any {
|
||||
return p.matchUserAgentVersion(/Android (\d+).(\d+)?/);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'ios',
|
||||
superset: 'mobile',
|
||||
subsets: [
|
||||
'ipad',
|
||||
'iphone'
|
||||
],
|
||||
settings: {
|
||||
autoFocusAssist: 'delay',
|
||||
clickBlock: true,
|
||||
hoverCSS: false,
|
||||
inputBlurring: isIOSDevice,
|
||||
inputCloning: isIOSDevice,
|
||||
keyboardHeight: 300,
|
||||
mode: 'ios',
|
||||
scrollAssist: isIOSDevice,
|
||||
statusbarPadding: !!(win.cordova),
|
||||
swipeBackEnabled: isIOSDevice,
|
||||
swipeBackThreshold: 40,
|
||||
tapPolyfill: isIOSDevice,
|
||||
virtualScrollEventAssist: !(win.indexedDB)
|
||||
},
|
||||
isMatch(p: Platform): boolean {
|
||||
return p.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
|
||||
},
|
||||
versionParser(p: Platform): any {
|
||||
return p.matchUserAgentVersion(/OS (\d+)_(\d+)?/);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'ipad',
|
||||
superset: 'tablet',
|
||||
settings: {
|
||||
keyboardHeight: 500,
|
||||
},
|
||||
isMatch(p: Platform): boolean {
|
||||
return p.isPlatformMatch('ipad');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'iphone',
|
||||
subsets: [
|
||||
'phablet'
|
||||
],
|
||||
isMatch(p: Platform): boolean {
|
||||
return p.isPlatformMatch('iphone');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'windows',
|
||||
superset: 'mobile',
|
||||
subsets: [
|
||||
'phablet',
|
||||
'tablet'
|
||||
],
|
||||
settings: {
|
||||
mode: 'wp',
|
||||
autoFocusAssist: 'immediate',
|
||||
clickBlock: true,
|
||||
hoverCSS: false
|
||||
},
|
||||
isMatch(p: Platform): boolean {
|
||||
return p.isPlatformMatch('windows', ['windows phone']);
|
||||
},
|
||||
versionParser(p: Platform): any {
|
||||
return p.matchUserAgentVersion(/Windows Phone (\d+).(\d+)?/);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Platform.register({
|
||||
name: 'cordova',
|
||||
isEngine: true,
|
||||
initialize: function(p: Platform, config: Config) {
|
||||
|
||||
// prepare a custom "ready" for cordova "deviceready"
|
||||
p.prepareReady = function() {
|
||||
// 1) ionic bootstrapped
|
||||
windowLoad(function() {
|
||||
// 2) window onload triggered or completed
|
||||
doc.addEventListener('deviceready', function() {
|
||||
// 3) cordova deviceready event triggered
|
||||
|
||||
// add cordova listeners to emit platform events
|
||||
doc.addEventListener('backbutton', function() {
|
||||
p.backButton.emit(null);
|
||||
});
|
||||
doc.addEventListener('pause', function() {
|
||||
p.pause.emit(null);
|
||||
});
|
||||
doc.addEventListener('resume', function() {
|
||||
p.resume.emit(null);
|
||||
});
|
||||
|
||||
// cordova has its own exitApp method
|
||||
p.exitApp = function() {
|
||||
win.navigator.app.exitApp();
|
||||
};
|
||||
|
||||
// cordova has fully loaded and we've added listeners
|
||||
p.triggerReady('cordova');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
},
|
||||
isMatch(): boolean {
|
||||
return !!(win.cordova || win.PhoneGap || win.phonegap);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function isIOSDevice(p: Platform) {
|
||||
// shortcut function to be reused internally
|
||||
// checks navigator.platform to see if it's an actual iOS device
|
||||
// this does not use the user-agent string because it is often spoofed
|
||||
// an actual iPad will return true, a chrome dev tools iPad will return false
|
||||
return p.testNavigatorPlatform('iphone|ipad|ipod');
|
||||
}
|
||||
3
src/platform/storage.ts
Normal file
3
src/platform/storage.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './storage/storage'
|
||||
export * from './storage/local-storage'
|
||||
export * from './storage/sql'
|
||||
100
src/platform/storage/local-storage.ts
Normal file
100
src/platform/storage/local-storage.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import {StorageEngine} from './storage';
|
||||
|
||||
|
||||
/**
|
||||
* @name LocalStorage
|
||||
* @description
|
||||
* The LocalStorage storage engine uses the browser's local storage system for
|
||||
* storing key/value pairs.
|
||||
*
|
||||
* Note: LocalStorage should ONLY be used for temporary data that you can afford to lose.
|
||||
* Given disk space constraints on a mobile device, local storage might be "cleaned up"
|
||||
* by the operating system (iOS).
|
||||
*
|
||||
* For guaranteed, long-term storage, use the SqlStorage engine which stores data in a file.
|
||||
*
|
||||
* @usage
|
||||
* ```ts
|
||||
* import {Page, Storage, LocalStorage} from 'ionic-angular';
|
||||
* @Page({
|
||||
* template: `<ion-content></ion-content>`
|
||||
* });
|
||||
* export class MyClass{
|
||||
* constructor(){
|
||||
* this.local = new Storage(LocalStorage);
|
||||
* this.local.set('didTutorial', 'true');
|
||||
* }
|
||||
*}
|
||||
*```
|
||||
* @demo /docs/v2/demos/local-storage/
|
||||
* @see {@link /docs/v2/platform/storage/ Storage Platform Docs}
|
||||
*/
|
||||
export class LocalStorage extends StorageEngine {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a key in LocalStorage
|
||||
* @param {string} key the key you want to lookup in LocalStorage
|
||||
* @returns {Promise} Returns a promise which is resolved when the value has been retrieved
|
||||
*/
|
||||
get(key: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let value = window.localStorage.getItem(key);
|
||||
resolve(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key value pair and save it to LocalStorage
|
||||
* @param {string} key the key you want to save to LocalStorage
|
||||
* @param {string} value the value of the key you're saving
|
||||
* @returns {Promise} Returns a promise which is resolved when the key value pair have been set
|
||||
*/
|
||||
set(key: string, value: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.setItem(key, value);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key from LocalStorage
|
||||
* @param {string} key the key you want to remove from LocalStorage
|
||||
* @returns {Promise} Returns a promise which is resolved when the key has been removed
|
||||
*/
|
||||
remove(key: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.removeItem(key);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear data stored in LocalStorage
|
||||
* @returns {Promise} Returns a promise which is resolved when the data have been cleared
|
||||
*/
|
||||
clear(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.clear();
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
150
src/platform/storage/sql.ts
Normal file
150
src/platform/storage/sql.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import {StorageEngine} from './storage';
|
||||
|
||||
import {defaults, assign} from '../../util/util';
|
||||
|
||||
const DB_NAME: string = '__ionicstorage';
|
||||
const win: any = window;
|
||||
|
||||
/**
|
||||
* SqlStorage uses SQLite or WebSQL (development only!) to store data in a
|
||||
* persistent SQL store on the filesystem.
|
||||
*
|
||||
* This is the preferred storage engine, as data will be stored in appropriate
|
||||
* app storage, unlike Local Storage which is treated differently by the OS.
|
||||
*
|
||||
* For convenience, the engine supports key/value storage for simple get/set and blob
|
||||
* storage. The full SQL engine is exposed underneath through the `query` method.
|
||||
*
|
||||
* @usage
|
||||
```js
|
||||
* let storage = new Storage(SqlStorage, options);
|
||||
* storage.set('name', 'Max');
|
||||
* storage.get('name').then((name) => {
|
||||
* });
|
||||
*
|
||||
* // Sql storage also exposes the full engine underneath
|
||||
* storage.query('insert into projects(name, data) values("Cool Project", "blah")');
|
||||
* storage.query('select * from projects').then((resp) => {})
|
||||
* ```
|
||||
*
|
||||
* The `SqlStorage` service supports these options:
|
||||
* {
|
||||
* name: the name of the database (__ionicstorage by default)
|
||||
* backupFlag: // where to store the file, default is BACKUP_LOCAL which DOES NOT store to iCloud. Other options: BACKUP_LIBRARY, BACKUP_DOCUMENTS
|
||||
* existingDatabase: whether to load this as an existing database (default is false)
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export class SqlStorage extends StorageEngine {
|
||||
static BACKUP_LOCAL = 2;
|
||||
static BACKUP_LIBRARY = 1;
|
||||
static BACKUP_DOCUMENTS = 0;
|
||||
|
||||
private _db: any;
|
||||
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
let dbOptions = defaults(options, {
|
||||
name: DB_NAME,
|
||||
backupFlag: SqlStorage.BACKUP_LOCAL,
|
||||
existingDatabase: false
|
||||
});
|
||||
|
||||
if (win.sqlitePlugin) {
|
||||
let location = this._getBackupLocation(dbOptions.backupFlag);
|
||||
|
||||
this._db = win.sqlitePlugin.openDatabase(assign({
|
||||
name: dbOptions.name,
|
||||
location: location,
|
||||
createFromLocation: dbOptions.existingDatabase ? 1 : 0
|
||||
}, dbOptions));
|
||||
|
||||
} else {
|
||||
console.warn('Storage: SQLite plugin not installed, falling back to WebSQL. Make sure to install cordova-sqlite-storage in production!');
|
||||
|
||||
this._db = win.openDatabase(dbOptions.name, '1.0', 'database', 5 * 1024 * 1024);
|
||||
}
|
||||
this._tryInit();
|
||||
}
|
||||
|
||||
_getBackupLocation(dbFlag: number): number {
|
||||
switch (dbFlag) {
|
||||
case SqlStorage.BACKUP_LOCAL:
|
||||
return 2;
|
||||
case SqlStorage.BACKUP_LIBRARY:
|
||||
return 1;
|
||||
case SqlStorage.BACKUP_DOCUMENTS:
|
||||
return 0;
|
||||
default:
|
||||
throw Error('Invalid backup flag: ' + dbFlag);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the DB with our required tables
|
||||
_tryInit() {
|
||||
this.query('CREATE TABLE IF NOT EXISTS kv (key text primary key, value text)').catch(err => {
|
||||
console.error('Storage: Unable to create initial storage tables', err.tx, err.err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an arbitrary SQL operation on the database. Use this method
|
||||
* to have full control over the underlying database through SQL operations
|
||||
* like SELECT, INSERT, and UPDATE.
|
||||
*
|
||||
* @param {string} query the query to run
|
||||
* @param {array} params the additional params to use for query placeholders
|
||||
* @return {Promise} that resolves or rejects with an object of the form { tx: Transaction, res: Result (or err)}
|
||||
*/
|
||||
query(query: string, params = []): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this._db.transaction((tx) => {
|
||||
tx.executeSql(query, params,
|
||||
(tx, res) => resolve({ tx: tx, res: res }),
|
||||
(tx, err) => reject({ tx: tx, err: err }));
|
||||
},
|
||||
(err) => reject({ err: err }));
|
||||
} catch (err) {
|
||||
reject({ err: err });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value in the database identified by the given key.
|
||||
* @param {string} key the key
|
||||
* @return {Promise} that resolves or rejects with an object of the form { tx: Transaction, res: Result (or err)}
|
||||
*/
|
||||
get(key: string): Promise<any> {
|
||||
return this.query('select key, value from kv where key = ? limit 1', [key]).then(data => {
|
||||
if (data.res.rows.length > 0) {
|
||||
return data.res.rows.item(0).value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value in the database for the given key. Existing values will be overwritten.
|
||||
* @param {string} key the key
|
||||
* @param {string} value The value (as a string)
|
||||
* @return {Promise} that resolves or rejects with an object of the form { tx: Transaction, res: Result (or err)}
|
||||
*/
|
||||
set(key: string, value: string): Promise<any> {
|
||||
return this.query('insert or replace into kv(key, value) values (?, ?)', [key, value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the value in the database for the given key.
|
||||
* @param {string} key the key
|
||||
* @return {Promise} that resolves or rejects with an object of the form { tx: Transaction, res: Result (or err)}
|
||||
*/
|
||||
remove(key: string): Promise<any> {
|
||||
return this.query('delete from kv where key = ?', [key]);
|
||||
}
|
||||
|
||||
clear(): Promise<any> {
|
||||
return this.query('delete from kv');
|
||||
}
|
||||
}
|
||||
85
src/platform/storage/storage.ts
Normal file
85
src/platform/storage/storage.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Storage is an easy way to store key/value pairs and other complicated
|
||||
* data in a way that uses a variety of storage engines underneath.
|
||||
*
|
||||
* For most cases, we recommend the SqlStorage system as it will store
|
||||
* data in a file in the app's sandbox. LocalStorage should ONLY be used
|
||||
* for temporary data as it may be 'cleaned up' by the operation system
|
||||
* during low disk space situations.
|
||||
*/
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class Storage {
|
||||
private _strategy: StorageEngine;
|
||||
|
||||
constructor(strategyCls: IStorageEngine, options?: any) {
|
||||
this._strategy = new strategyCls(options);
|
||||
}
|
||||
|
||||
get(key: string): Promise<any> {
|
||||
return this._strategy.get(key);
|
||||
}
|
||||
|
||||
getJson(key: string): Promise<any> {
|
||||
return this.get(key).then(value => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
console.warn('Storage getJson(): unable to parse value for key', key, ' as JSON');
|
||||
throw e; // rethrowing exception so it can be handled with .catch()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setJson(key: string, value: any): Promise<any> {
|
||||
try {
|
||||
return this.set(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
set(key: string, value: any) {
|
||||
return this._strategy.set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
return this._strategy.remove(key);
|
||||
}
|
||||
|
||||
query(query: string, params?: any) {
|
||||
return this._strategy.query(query, params);
|
||||
}
|
||||
|
||||
clear() {
|
||||
return this._strategy.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IStorageEngine {
|
||||
new (options: any): StorageEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class StorageEngine {
|
||||
constructor(options = {}) { }
|
||||
|
||||
get(key: string): Promise<any> {
|
||||
throw Error('get() not implemented for this storage engine');
|
||||
}
|
||||
set(key: string, value: any): Promise<any> {
|
||||
throw Error('set() not implemented for this storage engine');
|
||||
}
|
||||
remove(key: string): Promise<any> {
|
||||
throw Error('remove() not implemented for this storage engine');
|
||||
}
|
||||
query(query: string, params?: any): Promise<any> {
|
||||
throw Error('query() not implemented for this storage engine');
|
||||
}
|
||||
clear(): Promise<any> {
|
||||
throw Error('clear() not implemented for this storage engine');
|
||||
}
|
||||
}
|
||||
276
src/platform/test/platform.spec.ts
Normal file
276
src/platform/test/platform.spec.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import {Platform} from '../../../ionic';
|
||||
|
||||
export function run() {
|
||||
|
||||
it('should set core as the fallback', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent('idk');
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should get case insensitive querystring value', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?KEY=value');
|
||||
|
||||
expect(platform.query('key')).toEqual('value');
|
||||
});
|
||||
|
||||
it('should get querystring value', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?key=value');
|
||||
|
||||
expect(platform.query('key')).toEqual('value');
|
||||
});
|
||||
|
||||
it('should set windows via querystring', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=windows');
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(true);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set ios via querystring', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=ios');
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set windows via querystring, even with android user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=windows');
|
||||
platform.setUserAgent(ANDROID_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(true);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set ios via querystring, even with android user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=ios');
|
||||
platform.setUserAgent(ANDROID_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set android via querystring', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=android');
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set android via querystring, even with ios user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUrl('/?ionicplatform=android');
|
||||
platform.setUserAgent(IPHONE_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set windows platform via user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS_PHONE_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(true);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set windows platform via windows8 mobile user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS8_PHONE_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(true);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set windows platform via windows7 mobile user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS7_PHONE_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(true);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set android via user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(ANDROID_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(true);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set iphone via user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(IPHONE_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(true);
|
||||
expect(platform.is('iphone')).toEqual(true);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set ipad via user agent', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(IPAD_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(false);
|
||||
expect(platform.is('mobile')).toEqual(true);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(true);
|
||||
expect(platform.is('ipad')).toEqual(true);
|
||||
expect(platform.is('tablet')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set core platform for osx desktop firefox', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(OSX_10_FIREFOX_43_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set core platform for osx desktop safari', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(OSX_10_SAFARI_9_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set core platform for osx desktop chrome', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(OSX_10_CHROME_49_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set core platform for windows desktop chrome', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS_10_CHROME_40_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set core platform for windows desktop edge', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS_10_EDGE_12_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should set core platform for windows desktop IE', () => {
|
||||
let platform = new Platform();
|
||||
platform.setUserAgent(WINDOWS_8_IE_11_UA);
|
||||
platform.load(null);
|
||||
|
||||
expect(platform.is('core')).toEqual(true);
|
||||
expect(platform.is('mobile')).toEqual(false);
|
||||
expect(platform.is('windows')).toEqual(false);
|
||||
expect(platform.is('android')).toEqual(false);
|
||||
expect(platform.is('ios')).toEqual(false);
|
||||
expect(platform.is('ipad')).toEqual(false);
|
||||
expect(platform.is('tablet')).toEqual(false);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const OSX_10_FIREFOX_43_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0';
|
||||
const OSX_10_SAFARI_9_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4';
|
||||
const OSX_10_CHROME_49_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36';
|
||||
|
||||
const WINDOWS_10_CHROME_40_UA = 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36';
|
||||
const WINDOWS_10_EDGE_12_UA = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
|
||||
const WINDOWS_8_IE_11_UA = 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko';
|
||||
|
||||
const WINDOWS_PHONE_UA = 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 930) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537';
|
||||
const WINDOWS8_PHONE_UA = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)';
|
||||
const WINDOWS7_PHONE_UA = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; LG; GW910)';
|
||||
|
||||
const ANDROID_UA = 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.20 Mobile Safari/537.36';
|
||||
const IPHONE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4';
|
||||
const IPAD_UA = 'Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53';
|
||||
Reference in New Issue
Block a user