chore(build): rename ionic directory to src and update all references in the build process.

This commit is contained in:
Josh Thomas
2016-05-19 13:20:59 -05:00
parent 8470ae04ac
commit c8f760f080
595 changed files with 73 additions and 87 deletions

View 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);
}
}

View 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
View 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});
}
}

View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
export * from './storage/storage'
export * from './storage/local-storage'
export * from './storage/sql'

View 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
View 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');
}
}

View 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');
}
}

View 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';