diff --git a/bower_components/google-sheets/.bower.json b/bower_components/google-sheets/.bower.json new file mode 100644 index 0000000..c36429b --- /dev/null +++ b/bower_components/google-sheets/.bower.json @@ -0,0 +1,44 @@ +{ + "name": "google-sheets", + "version": "1.0.5", + "homepage": "https://googlewebcomponents.github.io/google-sheets", + "description": "Web components to interact with Google Sheets", + "main": "google-sheets.html", + "authors": [ + "Eric Bidelman <ebidel@gmail.com>" + ], + "license": "Apache2", + "ignore": [ + "/.*", + "/test/" + ], + "keywords": [ + "web-component", + "web-components", + "polymer", + "spreadsheets", + "google" + ], + "dependencies": { + "polymer": "Polymer/polymer#^1.1.2", + "google-apis": "GoogleWebComponents/google-apis#^1.1.0", + "google-signin": "GoogleWebComponents/google-signin#^1.0.3", + "iron-ajax": "PolymerElements/iron-ajax#^1.0.0" + }, + "devDependencies": { + "iron-component-page": "PolymerElements/iron-component-page#^1.0.0", + "google-map": "GoogleWebComponents/google-map#^1.1.0", + "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", + "web-component-tester": "*" + }, + "_release": "1.0.5", + "_resolution": { + "type": "version", + "tag": "1.0.5", + "commit": "1e87081a4abfdd31cf7cbf4ac970c7f9873c447d" + }, + "_source": "git://github.com/GoogleWebComponents/google-sheets.git", + "_target": "~1.0.5", + "_originalSource": "GoogleWebComponents/google-sheets", + "_direct": true +} \ No newline at end of file diff --git a/bower_components/google-sheets/LICENSE b/bower_components/google-sheets/LICENSE new file mode 100644 index 0000000..52aea39 --- /dev/null +++ b/bower_components/google-sheets/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bower_components/google-sheets/README.md b/bower_components/google-sheets/README.md new file mode 100755 index 0000000..119acf4 --- /dev/null +++ b/bower_components/google-sheets/README.md @@ -0,0 +1,4 @@ +google-sheets +================ + +See the [component page](https://googlewebcomponents.github.io/google-sheets) for more information. diff --git a/bower_components/google-sheets/bower.json b/bower_components/google-sheets/bower.json new file mode 100755 index 0000000..1b1428c --- /dev/null +++ b/bower_components/google-sheets/bower.json @@ -0,0 +1,34 @@ +{ + "name": "google-sheets", + "version": "1.0.5", + "homepage": "https://googlewebcomponents.github.io/google-sheets", + "description": "Web components to interact with Google Sheets", + "main": "google-sheets.html", + "authors": [ + "Eric Bidelman <ebidel@gmail.com>" + ], + "license": "Apache2", + "ignore": [ + "/.*", + "/test/" + ], + "keywords": [ + "web-component", + "web-components", + "polymer", + "spreadsheets", + "google" + ], + "dependencies": { + "polymer": "Polymer/polymer#^1.1.2", + "google-apis": "GoogleWebComponents/google-apis#^1.1.0", + "google-signin": "GoogleWebComponents/google-signin#^1.0.3", + "iron-ajax": "PolymerElements/iron-ajax#^1.0.0" + }, + "devDependencies": { + "iron-component-page": "PolymerElements/iron-component-page#^1.0.0", + "google-map": "GoogleWebComponents/google-map#^1.1.0", + "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", + "web-component-tester": "*" + } +} diff --git a/bower_components/google-sheets/demo/demo-private.html b/bower_components/google-sheets/demo/demo-private.html new file mode 100644 index 0000000..3ecc6d9 --- /dev/null +++ b/bower_components/google-sheets/demo/demo-private.html @@ -0,0 +1,106 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + <title>google-sheets private data demo</title> + <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script> + <link rel="import" href="../google-sheets.html"> + <link rel="import" href="../../google-map/google-map.html"> + <link rel="import" href="../../google-signin/google-signin.html"> + <style> + * { + box-sizing: border-box; + } + body { + margin: 2em; + font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial; + font-weight: 300; + background-color: #f1f1f3; + } + a { + text-decoration: none; + color: blue; + } + ul { + padding-left: 0; + } + ul, li { + list-style: none; + font-size: 14px; + } + section { + border-radius: 3px; + box-shadow: 1px 1px 3px #ccc; + padding: 1em 2em; + background-color: white; + width: 500px; + min-height: 500px; + } + main { + justify-content: space-around; + margin-top: 2em; + } + </style> +</head> +<body> + +<google-signin + client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com" + scopes="https://spreadsheets.google.com/feeds"> +</google-signin> + +<p>A <code><google-sheets></code> element returning data from a <b>private</b> Google Spreadsheet:</p> +<p><b>Note:</b> update the demo source to your clientId and private spreadsheet key for the demo to work.</p> + +<main class="layout horizontal"> + +<template id="spreadsheets" is="dom-bind"> + +<!-- Example: private spreadsheet --> +<google-sheets id="sheet" tab-id="1" + client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com" + key="1QMGizivw3UJ3-R9BFK7sfrXE0RL87dygk2C0RcuKoDY" + open-in-google-docs-url="{{openInGoogleDocsURL}}" + tab="{{tab}}" + spreadsheets="{{spreadsheets}}" + rows="{{rows}}"></google-sheets> + +<section> + + <heading> + <h3>List of spreadsheets</h3> + </heading> + + <ul> + <template is="dom-repeat" items="[[spreadsheets]]"> + <li>{{item.title.$t}}</li> + </template> + </ul> + +</section> + +<section> + + <heading> + <h3>Spreadsheet rows: + <a href="{{openInGoogleDocsURL}}" target="_blank" title="Open in Google Docs →"> + "<span>{{tab.title}}</span>" tab + </a> + </h3> + <h5>updated: <span>{{tab.updated}}</span>, by: <template is="dom-repeat" items="{{rows.authors}}"><span>{{item.name}}</span></template></h5> + </heading> + <ul> + <template is="dom-repeat" items="{{rows}}"> + <li>Name: <span>{{item.gsx$name.$t}}</span> ( lat: <span>{{item.gsx$lat.$t}}</span>, lng: <span>{{item.gsx$lng.$t}}</span> )</li> + </template> + </ul> + +</section> + +</template> + +</main> + +</body> +</html> diff --git a/bower_components/google-sheets/demo/index.html b/bower_components/google-sheets/demo/index.html new file mode 100755 index 0000000..623ef48 --- /dev/null +++ b/bower_components/google-sheets/demo/index.html @@ -0,0 +1,105 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + <title>google-sheets Demo</title> + <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script> + <link rel="import" href="../../iron-flex-layout/classes/iron-flex-layout.html"> + <link rel="import" href="../google-sheets.html"> + <link rel="import" href="../../google-map/google-map.html"> + <style> + * { + box-sizing: border-box; + } + body { + margin: 2em; + font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial; + font-weight: 300; + background-color: #f1f1f3; + } + a { + text-decoration: none; + color: blue; + } + ul { + padding-left: 0; + } + ul, li { + list-style: none; + font-size: 14px; + } + section { + border-radius: 3px; + box-shadow: 1px 1px 3px #ccc; + padding: 1em 2em; + background-color: white; + width: 500px; + height: 500px; + } + google-map { + display: block; + height: 100%; + width: 100%; + } + main { + justify-content: space-around; + margin-top: 2em; + } + </style> +</head> +<body> + +<p>A <code><google-sheets></code> element returns data from a Google Spreadsheet:</p> + +<main class="layout horizontal"> + +<template id="container" is="dom-bind"> + + <section> + + <!-- Example: published spreadsheet --> + <google-sheets id="sheet" key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" tab-id="1" + published rows="{{rows}}" tab="{{tab}}" open-in-google-docs-url="{{openInGoogleDocsUrl}}"></google-sheets> + + <heading> + <h3>Spreadsheet rows: + <a href="{{openInGoogleDocsUrl}}" target="_blank" title="Open in Google Docs →"> + "<span>{{tab.title}}</span>" tab + </a> + </h3> + <h5>updated: <span>{{tab.updated}}</span>, by: <template is="dom-repeat" items="{{rows.authors}}">{{item.name}}</template></h5> + </heading> + <ul> + <template is="dom-repeat" items="[[rows]]"> + <li>Name: <span>{{item.gsx$name.$t}}</span> ( lat: <span>{{item.gsx$lat.$t}}</span>, lng: <span>{{item.gsx$lng.$t}}</span> )</li> + </template> + </ul> + + </section> + + <section style="padding: 0;"> + + <google-map disable-default-ui fit-to-markers> + <template is="dom-repeat" items="[[rows]]"> + <google-map-marker latitude="{{item.gsx$lat.$t}}" longitude="{{item.gsx$lng.$t}}"></google-map-marker> + </template> + </google-map> + + <button on-click="useTab" data-tabid="1">View tab 1 data</button> + <button on-click="useTab" data-tabid="2">View tab 2 data</button> + + </section> + +</template> +</main> + +<script> +var template = document.querySelector('#container'); + +template.useTab = function(e, detail, sender) { + document.querySelector('#sheet').tabId = Number(e.currentTarget.dataset.tabid); +}; +</script> +</body> +</html> diff --git a/bower_components/google-sheets/google-sheets.html b/bower_components/google-sheets/google-sheets.html new file mode 100755 index 0000000..2bf9806 --- /dev/null +++ b/bower_components/google-sheets/google-sheets.html @@ -0,0 +1,431 @@ +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> + +<link rel="import" href="../polymer/polymer.html"> +<link rel="import" href="../iron-ajax/iron-ajax.html"> +<link rel="import" href="../google-signin/google-signin-aware.html"> + +<!-- +Element for interacting with Google Sheets. + +`<google-sheets>` pulls cell data from the Google Sheet specified by `key`. +A spreadsheet's key can be found in the URL when viewing it in google docs (e.g. `docs.google.com/spreadsheet/ccc?key=<KEY>#gid=12345`). + +Optionally, pass the `tab-id` attribute to specify a particular worksheet tab in the spreadsheet. For example, the first tab would be `tab-id="1"`. If `tab` is updated at a later time, the underlying data is also updated. **API calls are cached** as to not make extraneous calls. + +See [developers.google.com/google-apps/spreadsheets](https://developers.google.com/google-apps/spreadsheets) for full Spreadsheets API documentation. + +#### Example + + <google-sheets key="..." tab-id="1" client-id="..."></google-sheets> + + <script> + var sheet = document.querySelector('google-sheets'); + + sheet.addEventListener('google-sheet-data', function(e) { + // this.spreadsheets - list of the user's spreadsheets + // this.tab - information on the tab that was fetched + // this.rows - cell row information for the tab that was fetched + }); + + sheet.addEventListener('error', function(e) { + // e.detail.response + }); + </script> + +<b>Example</b> - `published` is a perf optimization and hints that the spreadsheet has been published (public): + + <google-sheets key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" published></google-sheets> + +<b>Example</b> - leaving off the `key` returns as list of the user's spreadsheets. + + <google-sheets client-id="..."></google-sheets> + +<b>Example</b> - show a list of Map markers, using data-binding features inside Polymer: + + <template is="dom-bind"> + <google-sheets + key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" tab-id="1" rows="{{rows}}" + client-id="..."> + </google-sheets> + <google-map> + <google-map-marker latitude="{{gsx$lat.$t}}" longitude="{{gsx$lng.$t}}"> + </google-map> + </template> + +<b>Example</b> - list a user's private spreadsheets. Authenticate with google-signin button. + + <google-signin + client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com" + scopes="https://spreadsheets.google.com/feeds"> + </google-signin> + + <template is="dom-bind"> + <google-sheets client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com" + key="1QMGizivw3UJ3-R9BFK7sfrXE0RL87dygk2C0RcuKoDY" tab-id="1" + spreadsheets="{{spreadsheets}}"></google-sheets> + <template is="dom-repeat" items="[[spreadsheets]]"> + <p>{{item.title.$t}}</p> + </template> + </template> + +@demo +--> + +<dom-module id="google-sheets"> + <template> + <template is="dom-if" if="{{!published}}"> + <google-signin-aware client-id="{{clientId}}" + scopes="https://spreadsheets.google.com/feeds" + on-google-signin-aware-success="_onSignInSuccess" + on-google-signin-aware-signed-out="_onSignInFail"></google-signin-aware> + </template> + + <iron-ajax id="publicajax" params='{"alt": "json"}' handle-as="json" + on-response="_onCellRows"></iron-ajax> + <iron-ajax id="listsheetsajax" params='{"alt": "json"}' handle-as="json" + on-response="_onSpreadsheetList"></iron-ajax> + <iron-ajax id="worksheetajax" params='{"alt": "json"}' handle-as="json" + on-response="_onWorksheet"></iron-ajax> + <iron-ajax id="cellrowsajax" params='{"alt": "json"}' handle-as="json" + on-response="_onCellRows"></iron-ajax> + + </template> +</dom-module> + +<script> +(function() { + var SCOPE_ = 'https://spreadsheets.google.com/feeds'; + + // Minimal cache for worksheet row data. Shared across instances so subsequent + // accesses are fast and API calls only happen once. + var rowDataCache_ = {}; + + function generateCacheKey_() { + return this._worksheetId + '_'+ this.tabId; + } + + function getLink_(rel, links) { + for (var i = 0, link; link = links[i]; ++i) { + if (link.rel === rel) { + return link; + } + } + return null; + } + + // Conversion of Worksheet Ids to GIDs and vice versa + // od4 > 2 + function wid_to_gid_(wid) { + return parseInt(String(wid), 36) ^ 31578; + } + // 2 > 0d4 + function gid_to_wid_(gid) { + // (gid xor 31578) encoded in base 36 + return parseInt((gid ^ 31578)).toString(36); + } + + window.GoogleSheets = Polymer({ + + is: 'google-sheets', + + /** + * Fired when the spreadsheet's cell information is available. + * + * @event google-sheet-data + * @param {Object} detail + * @param {Object} detail.data The data returned by the Spreadsheet API. + * @param {string} detail.type The type of data that was fetched. + * One of 'spreadsheets', 'tab', 'rows' * to correspond to the feed type. + */ + + hostAttributes: { + hidden: true + }, + + properties: { + /** + * A Google Developers client ID. Obtain from [console.developers.google.com](https://console.developers.google.com). Required for accessing a private spreadsheet. Optional if accessing a public spreadsheet. + */ + clientId: { + type: String, + value: '', + observer: '_configUpdate' + }, + + /** + * The key of the spreadsheet. This can be found in the URL when viewing + * the document is Google Docs (e.g. `docs.google.com/spreadsheet/ccc?key=<KEY>`). + * + * Leaving off this attribute still returns a list of the users spreadsheets in the `spreadsheets` property. + */ + key: { + type: String, + value: '', + observer: '_keyChanged' + }, + + /** + * Tab within a spreadsheet. For example, the first tab in a spreadsheet + * would be `tab-id="1"`. + */ + tabId: { + type: Number, + value: 1, + observer: '_configUpdate' + }, + + /** + * A hint that the spreadsheet is published publicly in Google Docs. Used as a performance optimization. + * Make sure the sheet is also publicly viewable by anyone in the Share settings. + * + * @attribute published + * @type boolean + * @default false + */ + published: { + type: Boolean, + value: false, + observer: '_configUpdate' + }, + + /** + * The fetched sheet corresponding to the `key` attribute. + */ + sheet: { + type: Object, + value: function() { return {}; }, + readOnly: true, + notify: true, + observer: '_sheetChanged' + }, + + /** + * Meta data about the particular tab that was retrieved for the spreadsheet. + */ + tab: { + type: Object, + value: function() { return {}; }, + readOnly: true, + notify: true, + observer: '_tabChanged' + }, + + /** + * If a spreadsheet `key` is specified, returns a list of cell row data. + */ + rows: { + type: Array, + value: function() { return []; }, + readOnly: true, + notify: true + }, + + /** + * List of the user's spreadsheets. Shared across instances. + */ + spreadsheets: { + type: Array, + readOnly: true, + notify: true, + value: function() { return []; } + }, + + /** + * The URL to open this spreadsheet in Google Sheets. + */ + openInGoogleDocsUrl: { + type: String, + computed: '_computeGoogleDocsUrl(key)', + notify: true + } + }, + + _worksheetId: null, + + _computeGoogleDocsUrl: function(key) { + var url = 'https://docs.google.com/spreadsheet/'; + if (key) { + url += 'ccc?key=' + key; + } + return url; + }, + + _configUpdate: function(key, published, tabId, clientId) { + this._tabIdChanged(); + }, + + _keyChanged: function(newValue, oldValue) { + // TODO(ericbidelman): need to better handle updates to the key attribute. + // Below doesn't account for private feeds. + if (this.published) { + var url = SCOPE_ + '/list/' + this.key + '/' + + this.tabId + '/public/values'; + this.$.publicajax.url = url; + this.$.publicajax.generateRequest(); + } + }, + + _tabIdChanged: function(newValue, oldValue) { + if (this._worksheetId) { + this._getCellRows(); + } else if (this.published) { + this._keyChanged(); + } + }, + + _sheetChanged: function(newValue, oldValue) { + if (!this.sheet.title) { + return; + } + + // Make metadata easily accessible on sheet object. + var authors = this.sheet.author && this.sheet.author.map(function(a) { + return {email: a.email.$t, name: a.name.$t}; + }); + + this.set('sheet.title', this.sheet.title.$t); + this.set('sheet.updated', new Date(this.sheet.updated.$t)); + this.set('sheet.authors', authors); + + this._worksheetId = this.sheet.id.$t.split('/').slice(-1)[0]; + this._getWorksheet(); + }, + + _tabChanged: function(newValue, oldValue) { + if (!this.tab.title) { + return; + } + + var authors = this.tab.authors = this.tab.author && this.tab.author.map(function(a) { + return {email: a.email.$t, name: a.name.$t}; + }); + + this.set('tab.title', this.tab.title.$t); + this.set('tab.updated', new Date(this.tab.updated.$t)); + this.set('tab.authors', authors); + + this.fire('google-sheet-data', { + type: 'tab', + data: this.tab + }); + }, + + _onSignInSuccess: function(e, detail) { + var oauthToken = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse(); + + var headers = { + 'Authorization': 'Bearer ' + oauthToken.access_token + }; + + this.$.listsheetsajax.headers = headers; + this.$.worksheetajax.headers = headers; + this.$.cellrowsajax.headers = headers; + + // TODO(ericbidelman): don't make this call if this.spreadsheets is + // already populated from another instance. + this._listSpreadsheets(); + }, + + _onSignInFail: function(e, detail) { + // TODO(ericbidelman): handle this in some way. + console.log(e, e.type); + }, + + _listSpreadsheets: function() { + var url = SCOPE_ + '/spreadsheets/private/full'; + this.$.listsheetsajax.url = url; + this.$.listsheetsajax.generateRequest(); + }, + + _onSpreadsheetList: function(e) { + e.stopPropagation(); + + var feed = e.target.lastResponse.feed; + + this._setSpreadsheets(feed.entry); + + this.fire('google-sheet-data', { + type: 'spreadsheets', + data: this.spreadsheets + }); + + // Fetch worksheet feed if key was given and worksheet exists. + if (this.key) { + for (var i = 0, entry; entry = feed.entry[i]; ++i) { + var altLink = getLink_('alternate', entry.link); + if (altLink && altLink.href.indexOf(this.key) != -1) { + this._setSheet(entry); + break; + } + } + } + }, + + _getWorksheet: function() { + if (!this._worksheetId) { + throw new Error('workesheetId was not given.'); + } + + var url = SCOPE_ + '/worksheets/' + this._worksheetId + + '/private/full/' + this.tabId; + this.$.worksheetajax.url = url; + this.$.worksheetajax.generateRequest(); + }, + + _onWorksheet: function(e) { + e.stopPropagation(); + + // this.tab = e.target.lastResponse.entry; + this._setTab(e.target.lastResponse.entry); + this._getCellRows(); + }, + + _getCellRows: function() { + // Use cached data if available. + var key = generateCacheKey_.call(this); + if (key in rowDataCache_) { + this._onCellRows(null, null, rowDataCache_[key]); + + return; + } + + var url = SCOPE_ + '/list/' + + this._worksheetId + '/' + this.tabId + + '/private/full'; + this.$.cellrowsajax.url = url; + this.$.cellrowsajax.generateRequest(); + }, + + _onCellRows: function(e) { + e.stopPropagation(); + + var feed = e.target.lastResponse.feed; + + // Cache data if key doesn't exist. + var key = generateCacheKey_.call(this); + if (!(key in rowDataCache_)) { + rowDataCache_[key] = {response: {feed: feed}}; + } + + // this.rows = feed.entry; + this._setRows(feed.entry); + var authors = feed.author && feed.author.map(function(a) { + return {email: a.email.$t, name: a.name.$t}; + }); + this.set('rows.authors', authors); + + if (this.published) { + // this.tab = feed; + this._setTab(feed); + } + + this.fire('google-sheet-data', { + type: 'rows', + data: this.rows + }); + } + + }); + +})(); +</script> diff --git a/bower_components/google-sheets/index.html b/bower_components/google-sheets/index.html new file mode 100755 index 0000000..203f4fa --- /dev/null +++ b/bower_components/google-sheets/index.html @@ -0,0 +1,15 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + + <script src="../webcomponentsjs/webcomponents-lite.js"></script> + <link rel="import" href="../iron-component-page/iron-component-page.html"> + +</head> +<body> + + <iron-component-page></iron-component-page> + +</body> +</html> diff --git a/bower_components/google-sheets/tests/google-sheet.html b/bower_components/google-sheets/tests/google-sheet.html new file mode 100644 index 0000000..f8544ef --- /dev/null +++ b/bower_components/google-sheets/tests/google-sheet.html @@ -0,0 +1,46 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + <meta charset="utf-8"> + <title>google-sheet tests</title> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + + <script src="../../../platform/platform.js"></script> + <link rel="import" href="../../../polymer-test-tools/tools.html"> + <script src="../../../polymer-test-tools/htmltest.js"></script> + + <link rel="import" href="../google-sheets.html"> +</head> +<body> + +<google-sheets id="sheet1"></google-sheets> + +<script> +document.addEventListener('polymer-ready', function() { + + (function() { + var sheet = document.querySelector('#sheet1'); + var root = sheet.shadowRoot; + + // Check defaults. + assert.lengthOf(sheet.spreadsheets, 0, + '.spreadsheets length should default to 0'); + assert.lengthOf(sheet.rows, 0, '.rows length should default to 0'); + assert.isObject(sheet.sheet, '.sheet should default to {}'); + assert.isObject(sheet.tab, '.tab should default to {}'); + + assert.equal(sheet.key, '', ".key default is not ''"); + assert.equal(sheet.gid, 0, '.gid default is not 0'); + assert.isFalse(sheet.published, '.published does not default to false'); + assert.isNotNull(root.querySelector('google-signin-aware'), + 'google-signin-aware should be created for non-public sheet'); + + done(); + + })(); + +}); +</script> +</body> +</html> diff --git a/bower_components/google-sheets/tests/index.html b/bower_components/google-sheets/tests/index.html new file mode 100644 index 0000000..297cdef --- /dev/null +++ b/bower_components/google-sheets/tests/index.html @@ -0,0 +1,14 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> + <head> + <meta charset="utf-8"> + <title>Runs all tests</title> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + <script src="../../platform/platform.js"></script> + <link rel="import" href="tests.html"> + </head> + <body> + <div id="mocha"></div> + </body> +</html> diff --git a/bower_components/google-sheets/tests/private.html b/bower_components/google-sheets/tests/private.html new file mode 100644 index 0000000..c6f3ee3 --- /dev/null +++ b/bower_components/google-sheets/tests/private.html @@ -0,0 +1,81 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + <meta charset="utf-8"> + <title>google-sheet tests</title> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + + <script src="../../../platform/platform.js"></script> + <link rel="import" href="../../../polymer-test-tools/tools.html"> + <script src="../../../polymer-test-tools/htmltest.js"></script> + + <link rel="import" href="../google-sheets.html"> +</head> +<body> + +<div> + <p>Tests live private spreadsheet data.</p> + <p>This test needs to be run manually, standalone in order to fetch an OAuth token.</p> + + <b>NOTES:</b><br> + <li>run manually off of localhost:3000/google-sheets/tests/private.html. Can also run from port 8080.</li> + <li>use the account: webcomponents.test@gmail.com</li> +</div> + +<google-sheets id="sheet" gid="1" + clientId="750497606405-1hq66meqmr4dp09dn54j9ggv85vbv0gp.apps.googleusercontent.com" + key="0AjqzxJ5RoRrWdGh2S0RRYURXTlNHdm9pQlFuM1ZwZWc"></google-sheets> + +<script> +document.addEventListener('polymer-ready', function() { + + (function() { + var sheet = document.querySelector('#sheet'); + var root = sheet.shadowRoot; + + // Test set attributes. + assert.equal(sheet.key, + '0AjqzxJ5RoRrWdGh2S0RRYURXTlNHdm9pQlFuM1ZwZWc', ".key was not updated"); + assert.equal(sheet.gid, 1, '.gid was not updated'); + assert.equal(sheet.clientId, + '750497606405-1hq66meqmr4dp09dn54j9ggv85vbv0gp.apps.googleusercontent.com', ".clientId was not set"); + + sheet.addEventListener('google-sheet-data', function(e) { + + switch (e.detail.type) { + case 'spreadsheets': + assert.isTrue(this.spreadsheets.length > 0, + '.spreadsheets should be populated for private feeds.'); + break; + case 'tab': + assert.equal(this.tab.title, 'SECONDTAB', '.tab.title is incorrect'); + break; + case 'rows': + assert.lengthOf(this.rows.authors, 1, '.rows.authors array'); + + var name = this.rows.authors[0].name; + var email = this.rows.authors[0].email + + assert.equal(email, 'webcomponents.test@gmail.com', 'author email not set correctly'); + assert.equal(name, 'webcomponents.test', 'author name not set correctly'); + + assert.equal(this.rows[0].title.$t, 'FIRST NAME', '"name" column was incorrect'); + assert.equal(this.rows[1].gsx$state.$t, 'OR', '"state" column was incorrect'); + + break; + default: + // Noop + } + + done(); + + }); + + })(); + +}); +</script> +</body> +</html> + diff --git a/bower_components/google-sheets/tests/published.html b/bower_components/google-sheets/tests/published.html new file mode 100644 index 0000000..067159a --- /dev/null +++ b/bower_components/google-sheets/tests/published.html @@ -0,0 +1,51 @@ +<!doctype html> +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> +<html> +<head> + <meta charset="utf-8"> + <title>google-sheet tests</title> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + + <script src="../../../platform/platform.js"></script> + <link rel="import" href="../../../polymer-test-tools/tools.html"> + <script src="../../../polymer-test-tools/htmltest.js"></script> + + <link rel="import" href="../google-sheets.html"> +</head> +<body> + +<google-sheets id="sheet" key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" + published></google-sheets> + +<script> +document.addEventListener('polymer-ready', function() { + + var sheet = document.querySelector('#sheet'); + var root = sheet.shadowRoot; + + assert.isTrue(sheet.published); + assert.isNull(root.querySelector('google-signin-aware'), + 'google-signin-aware should not be created for a published sheet'); + + sheet.addEventListener('google-sheet-data', function(e) { + + if (e.detail.type === 'tab') { + assert.equal(this.tab.title, 'Locations', + 'Published spreadsheet title is not correct.'); + assert.isNotNull(this.tab.updated, '.tab.updated was not set'); + assert.isTrue(this.tab.authors.length > 0, '.tab.authors was 0'); + } else if (e.detail.type === 'rows') { + assert.lengthOf(this.spreadsheets, 0, + '.spreadsheets length should be 0 since spreadsheet key was given'); + assert.isTrue(this.rows.length > 0, '.rows was not populated'); + } + + assert.equal(this.$.cellrowsajax.url, '', + '#cellrowsajax should not be invoked for a public spreadsheet'); + + done(); + }); +}); +</script> +</body> +</html> diff --git a/bower_components/google-sheets/tests/tests.html b/bower_components/google-sheets/tests/tests.html new file mode 100644 index 0000000..4241f19 --- /dev/null +++ b/bower_components/google-sheets/tests/tests.html @@ -0,0 +1,16 @@ +<!-- Copyright (c) 2015 Google Inc. All rights reserved. --> + +<link rel="import" href="../../polymer-test-tools/tools.html"> +<script src="../../polymer-test-tools/mocha-htmltest.js"></script> + +<script> +mocha.setup({ui: 'tdd', slow: 1000, timeout: 5000, htmlbase: ''}); + +htmlSuite('google-sheet', function() { + htmlTest('google-sheet.html'); + htmlTest('published.html'); + htmlTest('private.html'); +}); + +mocha.run(); +</script> diff --git a/bower_components/google-signin/.bower.json b/bower_components/google-signin/.bower.json new file mode 100644 index 0000000..4a4deff --- /dev/null +++ b/bower_components/google-signin/.bower.json @@ -0,0 +1,46 @@ +{ + "name": "google-signin", + "version": "1.2.0", + "description": "Web components to authenticate with Google services", + "homepage": "https://googlewebcomponents.github.io/google-signin", + "main": "google-signin.html", + "authors": [ + "Addy Osmani", + "Randy Merrill" + ], + "license": "Apache-2", + "ignore": [ + "/.*", + "/test/" + ], + "keywords": [ + "web-component", + "web-components", + "polymer", + "sign-in", + "google", + "authentication" + ], + "dependencies": { + "polymer": "Polymer/polymer#^1.0.0", + "font-roboto": "PolymerElements/font-roboto#^1.0.0", + "iron-icon": "PolymerElements/iron-icon#^1.0.0", + "iron-icons": "PolymerElements/iron-icons#^1.0.0", + "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", + "paper-ripple": "PolymerElements/paper-ripple#^1.0.0", + "paper-material": "PolymerElements/paper-material#^1.0.0", + "google-apis": "GoogleWebComponents/google-apis#^1.0.0" + }, + "devDependencies": { + "iron-component-page": "PolymerElements/iron-component-page#^1.0.2" + }, + "_release": "1.2.0", + "_resolution": { + "type": "version", + "tag": "v1.2.0", + "commit": "a1414b51b39e0a7dfe7031eb73a7733e2216579d" + }, + "_source": "git://github.com/GoogleWebComponents/google-signin.git", + "_target": "^1.0.3", + "_originalSource": "GoogleWebComponents/google-signin" +} \ No newline at end of file diff --git a/bower_components/google-signin/LICENSE b/bower_components/google-signin/LICENSE new file mode 100644 index 0000000..52aea39 --- /dev/null +++ b/bower_components/google-signin/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bower_components/google-signin/README.md b/bower_components/google-signin/README.md new file mode 100644 index 0000000..7c05417 --- /dev/null +++ b/bower_components/google-signin/README.md @@ -0,0 +1,4 @@ +google-signin +================ + +See the [component page](https://googlewebcomponents.github.io/google-signin) for more information. diff --git a/bower_components/google-signin/bower.json b/bower_components/google-signin/bower.json new file mode 100644 index 0000000..fecb402 --- /dev/null +++ b/bower_components/google-signin/bower.json @@ -0,0 +1,37 @@ +{ + "name": "google-signin", + "version": "1.2.0", + "description": "Web components to authenticate with Google services", + "homepage": "https://googlewebcomponents.github.io/google-signin", + "main": "google-signin.html", + "authors": [ + "Addy Osmani", + "Randy Merrill" + ], + "license": "Apache-2", + "ignore": [ + "/.*", + "/test/" + ], + "keywords": [ + "web-component", + "web-components", + "polymer", + "sign-in", + "google", + "authentication" + ], + "dependencies": { + "polymer": "Polymer/polymer#^1.0.0", + "font-roboto": "PolymerElements/font-roboto#^1.0.0", + "iron-icon": "PolymerElements/iron-icon#^1.0.0", + "iron-icons": "PolymerElements/iron-icons#^1.0.0", + "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", + "paper-ripple": "PolymerElements/paper-ripple#^1.0.0", + "paper-material": "PolymerElements/paper-material#^1.0.0", + "google-apis": "GoogleWebComponents/google-apis#^1.0.0" + }, + "devDependencies": { + "iron-component-page": "PolymerElements/iron-component-page#^1.0.2" + } +} diff --git a/bower_components/google-signin/demo/index.html b/bower_components/google-signin/demo/index.html new file mode 100644 index 0000000..7b0283f --- /dev/null +++ b/bower_components/google-signin/demo/index.html @@ -0,0 +1,150 @@ +<!doctype html> +<!-- Copyright (c) 2014 Google Inc. All rights reserved. --> +<html> +<head> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> + + <title>google-signin Demo</title> + + <script src="../../webcomponentsjs/webcomponents-lite.js"></script> + <link rel="import" href="../google-signin.html"> + <link rel="import" href="../google-signin-aware.html"> + + <!-- Demo only styles --> + <style> + body { + font-family: 'RobotoDraft', 'Roboto', sans-serif; + line-height:1.2; + vertical-align:middle; + background: rgba(204, 204, 204, 0.31); + } + + + .map { + background: whitesmoke; + margin: .5rem -1.5rem 0 -1.5rem; + padding: 0.5rem; + } + + h1 { + font-size: 2rem; + font-weight:200; + clear: both; + } + + h1 strong { + font-weight:300; + color:#539D00; + } + + h2 { + font-size:.9rem; + line-height:2.5; + color:gray; + font-weight:400; + clear: both; + } + + .showcase { + display: inline-block; + margin-right: 2rem; + float: left; + } + </style> + +</head> + +<body> + <p>A <code><google-signin></code> element looks like this button:</p> + + <p><google-signin brand="google" client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"></google-signin> +or like this if plus scopes are present + <google-signin brand="google-plus"></google-signin> + </p> + <p>Signin button can vary its appearance:</p> + <p>Width: + <google-signin brand="google" width="wide"></google-signin> + <google-signin brand="google" width="iconOnly"></google-signin> + Height: + <google-signin brand="google" height="tall"></google-signin> + <google-signin brand="google" height="standard"></google-signin> + <google-signin brand="google" height="short"></google-signin> + </p> + <p> + Theme: + <google-signin brand="google" theme="dark"></google-signin> + <google-signin brand="google" theme="light"></google-signin> + <google-signin brand="google-plus" theme="light"></google-signin> + <google-signin brand="google-plus" theme="light" raised></google-signin> + </p> + <!-- Demo the ability to use the google-signin-aware element. --> + <p><code><google-signin-aware></code> is a companion element.</p> + <p>You can use it inside your components to request additional scopes.</p> + <p>Every signin button will request all the scopes present in the document, + and change its appearance to match</p> + <p>For example, here is a signin-aware scope. You can change its scopes via popup</p> + <template id="awareness" is="dom-bind"> + <div><code><google-signin-aware + <div>scope= + <select value="{{scope::change}}"> + <option value="">None</option> + <option value="https://www.googleapis.com/auth/analytics">Google Analytics</option> + <option value="https://www.googleapis.com/auth/plus.login">Google Plus view circles</option> + <option value="https://www.googleapis.com/auth/youtube">YouTube</option> + <option value="https://www.googleapis.com/auth/calendar">Calendar</option> + <option value="profile">Profile info</option> + </select> + </div> + <div>offline=<input type="checkbox" checked="{{offline::change}}"></div> + <div>signedIn="<span>{{signedIn}}</span>"</div> + <div>isAuthorized="<span>{{isAuthorized}}</span>"</div> + <div>needAdditionalAuth:"<span>{{needAdditionalAuth}}</span>"></div> + </code></div> + <p>Every new scope you select will be added to requested scopes.</p> + <p>When you select a Google Plus scope, button will turn red.</p> + <google-signin></google-signin> + </p> + <google-signin-aware + scopes="{{scope}}" + signed-in="{{signedIn}}" + offline="{{offline}}" + is-authorized="{{isAuthorized}}" + need-additional-auth="{{needAdditionalAuth}}" + on-google-signin-aware-success="handleSignIn" + on-google-signin-offline-success="handleOffline" + on-google-signin-aware-signed-out="handleSignOut"></google-signin-aware> + <p>User name:<span>{{userName}}</span></p> + <p>Testing <code>google-signin-aware</code> events: <span>{{status}}</span></p> + <p>Testing <code>google-signin-offline</code> events: <span>{{offlineCode}}</span></p> + <p><button on-click="disconnect">Disconnect to start over</button></p> + </template> + <script> + var aware = document.querySelector('#awareness'); + aware.status = 'Not granted'; + aware.offlineCode = 'No offline login.'; + aware.userName = 'N/A'; + aware.handleSignIn = function(response) { + this.status = 'Signin granted'; + // console.log('[Aware] Signin Response', response); + this.userName = gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile().getName(); + }; + aware.handleOffline = function(response) { + this.offlineCode = response.detail.code; + }; + aware.handleSignOut = function(response) { + this.status = 'Signed out'; + // console.log('[Aware] Signout Response', response); + this.userName = 'N/A'; + }; + aware.disconnect = function() { + var b = document.querySelector('google-signin'); + var currentUser = gapi.auth2.getAuthInstance().currentUser.get(); + if (currentUser) { + currentUser.disconnect(); + } + gapi.auth2.getAuthInstance().signOut(); + }; + + </script> +</body> +</html> diff --git a/bower_components/google-signin/google-icons.html b/bower_components/google-signin/google-icons.html new file mode 100644 index 0000000..a9a463c --- /dev/null +++ b/bower_components/google-signin/google-icons.html @@ -0,0 +1,17 @@ +<!-- +Copyright (c) 2014 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at https://polymer.github.io/LICENSE.txt +The complete set of authors may be found at https://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at https://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at https://polymer.github.io/PATENTS.txt +--> + +<link rel="import" href="../iron-icon/iron-icon.html"> +<link rel="import" href="../iron-iconset-svg/iron-iconset-svg.html"> +<iron-iconset-svg name="google" iconSize="24"> +<svg><defs> +<g id="google"><path d="M16.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L18.9,0h-6.2C8.3,0,6.1,2.8,6.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C18.8,15.7,17.7,14.6,16.3,13.4z M8.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C10,9.7,8.7,6.6,8.7,4.3z M12.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C16.9,20.8,15,22.3,12.3,22.3z"/></g> +<g id="google-plus"><path d="M21,10V7h-2v3h-3v2h3v3h2v-3h3v-2H21z M13.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L15.9,0H9.7C5.3,0,3.1,2.8,3.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C15.8,15.7,14.7,14.6,13.3,13.4z M5.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C7,9.7,5.7,6.6,5.7,4.3z M9.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C13.9,20.8,12,22.3,9.3,22.3z"/></g> +</defs></svg> +</iron-iconset-svg> diff --git a/bower_components/google-signin/google-signin-aware.html b/bower_components/google-signin/google-signin-aware.html new file mode 100644 index 0000000..e28d8be --- /dev/null +++ b/bower_components/google-signin/google-signin-aware.html @@ -0,0 +1,703 @@ +<link rel="import" href="../polymer/polymer.html"> +<link rel="import" href="../google-apis/google-js-api.html"> + +<script> + (function() { + + /** + * Enum of attributes to be passed through to the login API call. + * @readonly + * @enum {string} + */ + var ProxyLoginAttributes = { + 'appPackageName': 'apppackagename', + 'clientId': 'clientid', + 'cookiePolicy': 'cookiepolicy', + 'requestVisibleActions': 'requestvisibleactions', + 'hostedDomain': 'hostedDomain' + }; + + /** + * AuthEngine does all interactions with gapi.auth2 + * + * It is tightly coupled with <google-signin-aware> element + * The elements configure AuthEngine. + * AuthEngine propagates all authentication events to all google-signin-aware elements + * + * API used: https://developers.google.com/identity/sign-in/web/reference + * + */ + var AuthEngine = { + + /** + * oauth2 argument, set by google-signin-aware + */ + _clientId: null, + + get clientId() { + return this._clientId; + }, + + set clientId(val) { + if (this._clientId && val && val != this._clientId) { + throw new Error('clientId cannot change. Values do not match. New: ' + val + ' Old:' + this._clientId); + } + if (val) { + this._clientId = val; + this.initAuth2(); + } + }, + + /** + * oauth2 argument, set by google-signin-aware + */ + _cookiePolicy: 'single_host_origin', + + get cookiePolicy() { + return this._cookiePolicy; + }, + + set cookiePolicy(val) { + if (val) { + this._cookiePolicy = val; + } + }, + + /** + * oauth2 argument, set by google-signin-aware + */ + _appPackageName: '', + + get appPackageName() { + return this._appPackageName; + }, + + set appPackageName(val) { + if (this._appPackageName && val && val != this._appPackageName) { + throw new Error('appPackageName cannot change. Values do not match. New: ' + val + ' Old: ' + this._appPackageName); + } + if (val) { + this._appPackageName = val; + } + }, + + /** + * oauth2 argument, set by google-signin-aware + */ + _requestVisibleActions: '', + + get requestVisibleactions() { + return this._requestVisibleActions; + }, + + set requestVisibleactions(val) { + if (this._requestVisibleActions && val && val != this._requestVisibleActions) { + throw new Error('requestVisibleactions cannot change. Values do not match. New: ' + val + ' Old: ' + this._requestVisibleActions); + } + if (val) + this._requestVisibleActions = val; + }, + + /** + * oauth2 argument, set by google-signin-aware + */ + _hostedDomain: '', + + get hostedDomain() { + return this._hostedDomain; + }, + + set hostedDomain(val) { + if (this._hostedDomain && val && val != this._hostedDomain) { + throw new Error('hostedDomain cannot change. Values do not match. New: ' + val + ' Old: ' + this._hostedDomain); + } + if (val) + this._hostedDomain = val; + }, + + /** Is offline access currently enabled in the google-signin-aware element? */ + _offline: false, + + get offline() { + return this._offline; + }, + + set offline(val) { + this._offline = val; + this.updateAdditionalAuth(); + }, + + /** Should we force a re-prompt for offline access? */ + _offlineAlwaysPrompt: false, + + get offlineAlwaysPrompt() { + return this._offlineAlwaysPrompt; + }, + + set offlineAlwaysPrompt(val) { + this._offlineAlwaysPrompt = val; + this.updateAdditionalAuth(); + }, + + /** Have we already gotten offline access from Google during this session? */ + offlineGranted: false, + + /** <google-js-api> */ + _apiLoader: null, + + /** an array of wanted scopes. oauth2 argument */ + _requestedScopeArray: [], + + /** _requestedScopeArray as string */ + get requestedScopes() { + return this._requestedScopeArray.join(' '); + }, + + /** Is user signed in? */ + _signedIn: false, + + /** Currently granted scopes */ + _grantedScopeArray: [], + + /** True if additional authorization is required */ + _needAdditionalAuth: true, + + /** True if have google+ scopes */ + _hasPlusScopes: false, + + /** + * array of <google-signin-aware> + * state changes are broadcast to them + */ + signinAwares: [], + + init: function() { + this._apiLoader = document.createElement('google-js-api'); + this._apiLoader.addEventListener('js-api-load', this.loadAuth2.bind(this)); + }, + + loadAuth2: function() { + gapi.load('auth2', this.initAuth2.bind(this)); + }, + + initAuth2: function() { + if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId) { + return; + } + var auth = gapi.auth2.init({ + 'client_id': this.clientId, + 'cookie_policy': this.cookiePolicy, + 'scope': this.requestedScopes, + 'hosted_domain': this.hostedDomain + }); + + auth.currentUser.listen(this.handleUserUpdate.bind(this)); + + auth.then( + function success() { + // Let the current user listener trigger the changes. + }, + function error(error) { + console.error(error); + } + ); + }, + + handleUserUpdate: function(newPrimaryUser) { + // update and broadcast currentUser + var isSignedIn = newPrimaryUser.isSignedIn(); + if (isSignedIn != this._signedIn) { + this._signedIn = isSignedIn; + for (var i=0; i<this.signinAwares.length; i++) { + this.signinAwares[i]._setSignedIn(isSignedIn); + } + } + + // update granted scopes + this._grantedScopeArray = this.strToScopeArray( + newPrimaryUser.getGrantedScopes()); + // console.log(this._grantedScopeArray); + this.updateAdditionalAuth(); + + var response = newPrimaryUser.getAuthResponse(); + for (var i=0; i<this.signinAwares.length; i++) { + this.signinAwares[i]._updateScopeStatus(response); + } + }, + + setOfflineCode: function(code) { + for (var i=0; i<this.signinAwares.length; i++) { + this.signinAwares[i]._updateOfflineCode(code); + } + }, + + /** convert scope string to scope array */ + strToScopeArray: function(str) { + if (!str) { + return []; + } + // remove extra spaces, then split + var scopes = str.replace(/\ +/g, ' ').trim().split(' '); + for (var i=0; i<scopes.length; i++) { + scopes[i] = scopes[i].toLowerCase(); + // Handle scopes that will be deprecated but are still returned with their old value + if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.profile') { + scopes[i] = 'profile'; + } + if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.email') { + scopes[i] = 'email'; + } + } + // return with duplicates filtered out + return scopes.filter( function(value, index, self) { + return self.indexOf(value) === index; + }); + }, + + /** true if scopes have google+ scopes */ + isPlusScope: function(scope) { + return (scope.indexOf('/auth/games') > -1) + || (scope.indexOf('auth/plus.') > -1 && scope.indexOf('auth/plus.me') < 0); + }, + + /** true if scopes have been granted */ + hasGrantedScopes: function(scopeStr) { + var scopes = this.strToScopeArray(scopeStr); + for (var i=0; i< scopes.length; i++) { + if (this._grantedScopeArray.indexOf(scopes[i]) === -1) + return false; + } + return true; + }, + + /** request additional scopes */ + requestScopes: function(newScopeStr) { + var newScopes = this.strToScopeArray(newScopeStr); + var scopesUpdated = false; + for (var i=0; i<newScopes.length; i++) { + if (this._requestedScopeArray.indexOf(newScopes[i]) === -1) { + this._requestedScopeArray.push(newScopes[i]); + scopesUpdated = true; + } + } + if (scopesUpdated) { + this.updateAdditionalAuth(); + this.updatePlusScopes(); + } + }, + + /** update status of _needAdditionalAuth */ + updateAdditionalAuth: function() { + var needMoreAuth = false; + if ((this.offlineAlwaysPrompt || this.offline ) && !this.offlineGranted) { + needMoreAuth = true; + } else { + for (var i=0; i<this._requestedScopeArray.length; i++) { + if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) === -1) { + needMoreAuth = true; + break; + } + } + } + if (this._needAdditionalAuth != needMoreAuth) { + this._needAdditionalAuth = needMoreAuth; + // broadcast new value + for (var i=0; i<this.signinAwares.length; i++) { + this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth); + } + } + }, + + updatePlusScopes: function() { + var hasPlusScopes = false; + for (var i = 0; i < this._requestedScopeArray.length; i++) { + if (this.isPlusScope(this._requestedScopeArray[i])) { + hasPlusScopes = true; + break; + } + } + if (this._hasPlusScopes != hasPlusScopes) { + this._hasPlusScopes = hasPlusScopes; + for (var i=0; i<this.signinAwares.length; i++) { + this.signinAwares[i]._setHasPlusScopes(hasPlusScopes); + } + } + }, + /** + * attached <google-signin-aware> + * @param {<google-signin-aware>} aware element to add + */ + attachSigninAware: function(aware) { + if (this.signinAwares.indexOf(aware) == -1) { + this.signinAwares.push(aware); + // Initialize aware properties + aware._setNeedAdditionalAuth(this._needAdditionalAuth); + aware._setSignedIn(this._signedIn); + aware._setHasPlusScopes(this._hasPlusScopes); + } else { + console.warn('signinAware attached more than once', aware); + } + }, + + detachSigninAware: function(aware) { + var index = this.signinAwares.indexOf(aware); + if (index != -1) { + this.signinAwares.splice(index, 1); + } else { + console.warn('Trying to detach unattached signin-aware'); + } + }, + + /** returns scopes not granted */ + getMissingScopes: function() { + return this._requestedScopeArray.filter( function(scope) { + return this._grantedScopeArray.indexOf(scope) === -1; + }.bind(this)).join(' '); + }, + + assertAuthInitialized: function() { + if (!this.clientId) { + throw new Error("AuthEngine not initialized. clientId has not been configured."); + } + if (!('gapi' in window)) { + throw new Error("AuthEngine not initialized. gapi has not loaded."); + } + if (!('auth2' in window.gapi)) { + throw new Error("AuthEngine not initialized. auth2 not loaded."); + } + }, + + /** pops up sign-in dialog */ + signIn: function() { + this.assertAuthInitialized(); + var params = { + 'scope': this.getMissingScopes() + }; + + // Proxy specific attributes through to the signIn options. + Object.keys(ProxyLoginAttributes).forEach(function(key) { + if (this[key] && this[key] !== '') { + params[ProxyLoginAttributes[key]] = this[key]; + } + }, this); + + var promise; + var user = gapi.auth2.getAuthInstance().currentUser.get(); + if (!(this.offline || this.offlineAlwaysPrompt)) { + if (user.getGrantedScopes()) { + // additional auth, skip multiple account dialog + promise = user.grant(params); + } else { + // initial signin + promise = gapi.auth2.getAuthInstance().signIn(params); + } + } else { + params.redirect_uri = 'postmessage'; + if (this.offlineAlwaysPrompt) { + params.approval_prompt = 'force'; + } + + // Despite being documented at https://goo.gl/tiO0Bk + // It doesn't seem like user.grantOfflineAccess() actually exists in + // the current version of the Google Sign-In JS client we're using + // through GoogleWebComponents. So in the offline case, we will not + // distinguish between a first auth and an additional one. + promise = gapi.auth2.getAuthInstance().grantOfflineAccess(params); + } + promise.then( + function success(response) { + // If login was offline, response contains one string "code" + // Otherwise it contains the user object already + var newUser; + if (response.code) { + AuthEngine.offlineGranted = true; + newUser = gapi.auth2.getAuthInstance().currentUser.get(); + AuthEngine.setOfflineCode(response.code); + } else { + newUser = response; + } + + var authResponse = newUser.getAuthResponse(); + // Let the current user listener trigger the changes. + }, + function error(error) { + if ("Access denied." == error.reason) { + // Access denied is not an error, user hit cancel + return; + } else { + console.error(error); + } + } + ); + }, + + /** signs user out */ + signOut: function() { + this.assertAuthInitialized(); + gapi.auth2.getAuthInstance().signOut().then( + function success() { + // Let the current user listener trigger the changes. + }, + function error(error) { + console.error(error); + } + ); + } + }; + + AuthEngine.init(); + +/** +`google-signin-aware` is used to enable authentication in custom elements by +interacting with a google-signin element that needs to be present somewhere +on the page. + +The `scopes` attribute allows you to specify which scope permissions are required +(e.g do you want to allow interaction with the Google Drive API). + +The `google-signin-aware-success` event is triggered when a user successfully +authenticates. If either `offline` or `offlineAlwaysPrompt` is set to true, successful +authentication will also trigger the `google-signin-offline-success`event. +The `google-signin-aware-signed-out` event is triggered when a user explicitly +signs out via the google-signin element. + +You can bind to `isAuthorized` property to monitor authorization state. +##### Example + + <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google-signin-aware> + + +##### Example with offline + <template id="awareness" is="dom-bind"> + <google-signin-aware + scopes="https://www.googleapis.com/auth/drive" + offline + on-google-signin-aware-success="handleSignin" + on-google-signin-offline-success="handleOffline"></google-signin-aware> + <\/template> + <script> + var aware = document.querySelector('#awareness'); + aware.handleSignin = function(response) { + var user = gapi.auth2.getAuthInstance().currentUser.get(); + console.log('User name: ' + user.getBasicProfile().getName()); + }; + aware.handleOffline = function(response) { + console.log('Offline code received: ' + response.detail.code); + // Here you would POST response.detail.code to your webserver, which can + // exchange the authorization code for an access token. More info at: + // https://developers.google.com/identity/protocols/OAuth2WebServer + }; + <\/script> +*/ + Polymer({ + + is: 'google-signin-aware', + + /** + * Fired when this scope has been authorized + * @param {Object} result Authorization result. + * @event google-signin-aware-success + */ + /** + * Fired when an offline authorization is successful. + * @param {Object} detail + * @param {string} detail.code The one-time authorization code from Google. + * Your application can exchange this for an `access_token` and `refresh_token` + * @event google-signin-offline-success + */ + /** + * Fired when this scope is not authorized + * @event google-signin-aware-signed-out + */ + properties: { + /** + * App package name for android over-the-air installs. + * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs) + */ + appPackageName: { + type: String, + observer: '_appPackageNameChanged' + }, + /** + * a Google Developers clientId reference + */ + clientId: { + type: String, + observer: '_clientIdChanged' + }, + + /** + * The cookie policy defines what URIs have access to the session cookie + * remembering the user's sign-in state. + * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information. + * @default 'single_host_origin' + */ + cookiePolicy: { + type: String, + observer: '_cookiePolicyChanged' + }, + + /** + * The app activity types you want to write on behalf of the user + * (e.g http://schemas.google.com/AddActivity) + * + */ + requestVisibleActions: { + type: String, + observer: '_requestVisibleActionsChanged' + }, + + /** + * The Google Apps domain to which users must belong to sign in. + * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information. + */ + hostedDomain: { + type: String, + observer: '_hostedDomainChanged' + }, + + /** + * Allows for offline `access_token` retrieval during the signin process. + * See also `offlineAlwaysPrompt`. You only need to set one of the two; if both + * are set, the behavior of `offlineAlwaysPrompt` will override `offline`. + */ + offline: { + type: Boolean, + value: false, + observer: '_offlineChanged' + }, + + /** + * Works the same as `offline` with the addition that it will always + * force a re-prompt to the user, guaranteeing that you will get a + * refresh_token even if the user has already granted offline access to + * this application. You only need to set one of `offline` or + * `offlineAlwaysPrompt`, not both. + */ + offlineAlwaysPrompt: { + type: Boolean, + value: false, + observer: '_offlineAlwaysPromptChanged' + }, + + /** + * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive) + * and should be space-delimited. + */ + scopes: { + type: String, + value: 'profile', + observer: '_scopesChanged' + }, + + /** + * True if user is signed in + */ + signedIn: { + type: Boolean, + notify: true, + readOnly: true + }, + + /** + * True if authorizations for *this* element have been granted + */ + isAuthorized: { + type: Boolean, + notify: true, + readOnly: true, + value: false + }, + + /** + * True if additional authorizations for *any* element are required + */ + needAdditionalAuth: { + type: Boolean, + notify: true, + readOnly: true + }, + + /** + * True if *any* element has google+ scopes + */ + hasPlusScopes: { + type: Boolean, + value: false, + notify: true, + readOnly: true + } + }, + + attached: function() { + AuthEngine.attachSigninAware(this); + }, + + detached: function() { + AuthEngine.detachSigninAware(this); + }, + + /** pops up the authorization dialog */ + signIn: function() { + AuthEngine.signIn(); + }, + + /** signs user out */ + signOut: function() { + AuthEngine.signOut(); + }, + + _appPackageNameChanged: function(newName, oldName) { + AuthEngine.appPackageName = newName; + }, + + _clientIdChanged: function(newId, oldId) { + AuthEngine.clientId = newId; + }, + + _cookiePolicyChanged: function(newPolicy, oldPolicy) { + AuthEngine.cookiePolicy = newPolicy; + }, + + _requestVisibleActionsChanged: function(newVal, oldVal) { + AuthEngine.requestVisibleActions = newVal; + }, + + _hostedDomainChanged: function(newVal, oldVal) { + AuthEngine.hostedDomain = newVal; + }, + + _offlineChanged: function(newVal, oldVal) { + AuthEngine.offline = newVal; + }, + + _offlineAlwaysPromptChanged: function(newVal, oldVal) { + AuthEngine.offlineAlwaysPrompt = newVal; + }, + + _scopesChanged: function(newVal, oldVal) { + AuthEngine.requestScopes(newVal); + this._updateScopeStatus(); + }, + + _updateScopeStatus: function(user) { + var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.scopes); + if (newAuthorized !== this.isAuthorized) { + this._setIsAuthorized(newAuthorized); + if (newAuthorized) { + this.fire('google-signin-aware-success', user); + } + else { + this.fire('google-signin-aware-signed-out', user); + } + } + }, + + _updateOfflineCode: function(code) { + if (code) { + this.fire('google-signin-offline-success', {code: code}); + } + } + }); + })(); +</script> diff --git a/bower_components/google-signin/google-signin.css b/bower_components/google-signin/google-signin.css new file mode 100644 index 0000000..2b98356 --- /dev/null +++ b/bower_components/google-signin/google-signin.css @@ -0,0 +1,231 @@ +:host { + display: inline-block; + position: relative; + box-sizing: border-box; + margin: 0 0.29em; + background: transparent; + text-align: center; + font: inherit; + outline: none; + border-radius: 3px; + -webkit-user-select: none; + user-select: none; + cursor: pointer; + z-index: 0; +} + +:host([disabled]) { + cursor: auto; + pointer-events: none; +} + +:host([disabled]) #button { + background: #eaeaea; + color: #a8a8a8; +} + +#button { + position: relative; + outline: none; + font-size: 14px; + font-weight: 400; + font-family: 'RobotoDraft','Roboto',arial,sans-serif; + white-space: nowrap; + border-radius: inherit; +} + +iron-icon { + width: 22px; + height: 22px; + margin: 6px; +} + +.icon { + display: inline-block; + vertical-align: middle; +} + +#shadow { + border-radius: inherit; +} + +#ripple { + pointer-events: none; +} + +.button-content { + outline: none; +} + +.buttonText { + display: inline-block; + vertical-align: middle; + padding-right: .8em; +} + +/* + * Dark Theme + */ +.theme-dark { + background: #da4336; + color: #ffffff; + border: 1px solid transparent; +} + +.theme-dark.signedIn-true.additionalAuth-false { + background: #999; + border: 1px solid #888; +} + +.theme-dark.signedIn-true.additionalAuth-false:hover, +.theme-dark.signedIn-true.additionalAuth-false:focus { + background: #aaa; +} + +:host([noink]) .theme-dark:hover, +:host([noink]) .theme-dark:focus { + background: #e74b37; +} + +:host([noink]) .theme-dark.signedIn-true.additionalAuth-false:hover, +:host([noink]) .theme-dark.signedIn-true.additionalAuth-false:focus { + background: #aaa; +} + +/* + * Light Theme + */ +.theme-light { + background: #fff; + color: #737373; + border: 1px solid #d9d9d9; +} + +.theme-light.signedIn-true.additionalAuth-false { + background: #c0c0c0; + color: #fff; + border: #888 1px solid; +} + +.theme-light.signedIn-true.additionalAuth-false:hover, +.theme-light.signedIn-true.additionalAuth-false:focus { + background: #aaa; +} + +:host([noink]) .theme-light .button-content:hover, +:host([noink]) .theme-light:focus { + border: 1px solid #c0c0c0; +} + +:host([noink]) .theme-light.signedIn-true.additionalAuth-false:hover, +:host([noink]) .theme-light.signedIn-true.additionalAuth-false:focus { + background: #aaa; +} + +/* + * Icon Only Width + */ +.width-iconOnly .buttonText { + display: none; +} + +/* + * Tall Height + */ +.height-tall .buttonText { + font-size: 15px; + font-weight: 700; +} + +.height-tall iron-icon { + width: 30px; + height: 30px; + margin: 8px; +} + +/* + * Short Height + */ +.height-short .buttonText { + font-size: 11px; +} + +.height-short iron-icon { + width: 16px; + height: 16px; + margin: 3px; +} + + +/* + * Branding + */ + +/* Google Scopes */ + +/* Dark Theme */ +.brand-google.theme-dark { + background: #4184F3; + color: #fff; + border: 1px solid #3266d5; +} + +.brand-google.theme-dark #ripple { + color: #1b39a8; +} + +:host([noink]) .brand-google.theme-dark:hover, +:host([noink]) .brand-google.theme-dark:focus { + background: #e74b37; +} + +.brand-google.theme-light .icon { + color: #4184F3; +} + +.brand-google.theme-light.signedIn-true.additionalAuth-false .icon { + color: #fff; +} + +.brand-google.theme-light #ripple { + color: #444; +} + +:host([noink]) .brand-google.theme-light:hover, +:host([noink]) .brand-google.theme-light:focus { + border: 1px solid #c0c0c0; +} + +.brand-google-plus.theme-dark { + background: #da4336; + color: #fff; + border: 1px solid transparent; +} + +.brand-google-plus.theme-dark #ripple { + color: #c43828; +} + +/* Light Theme */ +.brand-google-plus.theme-light { + background: #fff; + color: #737373; + border: 1px solid #d9d9d9; +} + +.brand-google-plus.theme-light .icon { + color: #e74b37; +} + +.brand-google-plus.theme-light.signedIn-true.additionalAuth-false .icon { + color: #fff; +} + +.brand-google-plus.theme-light #ripple { + color: #400; +} + +:host([noink]) .brand-google-plus.theme-light:hover, +:host([noink]) .brand-google-plus.theme-light:focus { + border: 1px solid #c0c0c0; +} diff --git a/bower_components/google-signin/google-signin.html b/bower_components/google-signin/google-signin.html new file mode 100644 index 0000000..b8496df --- /dev/null +++ b/bower_components/google-signin/google-signin.html @@ -0,0 +1,539 @@ +<link rel="import" href="../polymer/polymer.html"> +<link rel="import" href="google-signin-aware.html"> +<link rel="import" href="../iron-icon/iron-icon.html"> +<link rel="import" href="../iron-icons/iron-icons.html"> +<link rel="import" href="../font-roboto/roboto.html"> +<link rel="import" href="../google-apis/google-js-api.html"> +<link rel="import" href="../paper-ripple/paper-ripple.html"> +<link rel="import" href="../paper-material/paper-material.html"> +<link rel="import" href="../iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="google-icons.html"> + +<dom-module id="google-signin"> + <link rel="import" type="css" href="google-signin.css"> + <template> + <google-signin-aware id="aware" + app-package-name="{{appPackageName}}" + client-id="{{clientId}}" + cookie-policy="{{cookiePolicy}}" + request-visible-actions="{{requestVisibleActions}}" + hosted-domain="{{hostedDomain}}" + offline="{{offline}}" + offline-always-prompt="{{offlineAlwaysPrompt}}" + scopes="{{scopes}}" + signed-in="{{signedIn}}" + is-authorized="{{isAuthorized}}" + need-additional-auth="{{needAdditionalAuth}}" + has-plus-scopes="{{hasPlusScopes}}"></google-signin-aware> + <template is="dom-if" if="{{raised}}"> + <paper-material id="shadow" class="fit" elevation="2" animated></paper-material> + </template> + <div id="button" + class$="[[_computeButtonClass(height, width, theme, signedIn, _brand, needAdditionalAuth)]]"> + + <paper-ripple id="ripple" class="fit"></paper-ripple> + <!-- this div is needed to position the ripple behind text content --> + <div relative layout horizontal center-center> + <template is="dom-if" if="{{_computeButtonIsSignIn(signedIn, needAdditionalAuth)}}"> + <div class="button-content signIn" tabindex="0" + on-click="signIn" on-keydown="_signInKeyPress"> + <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> + <span class="buttonText">{{_labelSignin}}</span> + </div> + </template> + <template is="dom-if" if="{{_computeButtonIsSignOut(signedIn, needAdditionalAuth) }}"> + <div class="button-content signOut" tabindex="0" + on-click="signOut" on-keydown="_signOutKeyPress"> + <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> + <span class="buttonText">{{labelSignout}}</span> + </div> + </template> + <template is="dom-if" if="{{_computeButtonIsSignOutAddl(signedIn, needAdditionalAuth) }}"> + <div class="button-content signIn" tabindex="0" + on-click="signIn" on-keydown="_signInKeyPress"> + <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span> + <span class="buttonText">{{labelAdditional}}</span> + </div> + </template> + </div> + + </div> + </template> +</dom-module> +<script> + (function() { + + /** + * Enum brand values. + * @readonly + * @enum {string} + */ + var BrandValue = { + GOOGLE: 'google', + PLUS: 'google-plus' + }; + + /** + * Enum height values. + * @readonly + * @enum {string} + */ + var HeightValue = { + SHORT: 'short', + STANDARD: 'standard', + TALL: 'tall' + }; + + /** + * Enum button label default values. + * @readonly + * @enum {string} + */ + var LabelValue = { + STANDARD: 'Sign in', + WIDE: 'Sign in with Google', + WIDE_PLUS: 'Sign in with Google+' + }; + + /** + * Enum theme values. + * @readonly + * @enum {string} + */ + var ThemeValue = { + LIGHT: 'light', + DARK: 'dark' + }; + + /** + * Enum width values. + * @readonly + * @enum {string} + */ + var WidthValue = { + ICON_ONLY: 'iconOnly', + STANDARD: 'standard', + WIDE: 'wide' + }; + +/** +<google-signin> is used to authenticate with Google, allowing you to interact +with other Google APIs such as Drive and Google+. + +<img style="max-width:100%;" src="https://cloud.githubusercontent.com/assets/107076/6791176/5c868822-d16a-11e4-918c-ec9b84a2db45.png"/> + +If you do not need to show the button, use companion `<google-signin-aware>` element to declare scopes, check authentication state. + +#### Examples + + <google-signin client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> + + <google-signin label-signin="Sign-in" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> + + <google-signin theme="dark" width="iconOnly" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin> + + +#### Notes + +The attribute `clientId` is provided in your Google Developers Console +(https://console.developers.google.com). + +The `scopes` attribute allows you to specify which scope permissions are required +(e.g do you want to allow interaction with the Google Drive API). Many APIs also +need to be enabled in the Google Developers Console before you can use them. + +The `requestVisibleActions` attribute is necessary if you want to write app +activities (https://developers.google.com/+/web/app-activities/) on behalf of +the user. Please note that this attribute is only valid in combination with the +plus.login scope (https://www.googleapis.com/auth/plus.login). + +The `offline` attribute allows you to get an auth code which your server can +redeem for an offline access token +(https://developers.google.com/identity/sign-in/web/server-side-flow). +You can also set `offline-always-prompt` instead of `offline` to ensure that your app +will re-prompt the user for offline access and generate a working `refresh_token` +even if they have already granted offline access to your app in the past. + +Use label properties to customize prompts. + +The button can be styled in using the `height`, `width`, and `theme` attributes. +These attributes help you follow the Google+ Sign-In button branding guidelines +(https://developers.google.com/+/branding-guidelines). + +The `google-signin-success` event is triggered when a user successfully authenticates +and `google-signed-out` is triggered when user signs out. +You can also use `isAuthorized` attribute to observe user's authentication state. + +Additional events, such as `google-signout-attempted` are +triggered when the user attempts to sign-out and successfully signs out. + +When requesting offline access, the `google-signin-offline-success` event is +triggered when the user successfully consents with offline support. + +The `google-signin-necessary` event is fired when scopes requested via +google-signin-aware elements require additional user permissions. + +#### Testing + +By default, the demo accompanying this element is setup to work on localhost with +port 8080. That said, you *should* update the `clientId` to your own one for +any apps you're building. See the Google Developers Console +(https://console.developers.google.com) for more info. + +@demo +*/ + + Polymer({ + + is: 'google-signin', + + /** + * Fired when user is signed in. + * You can use auth2 api to retrieve current user: `gapi.auth2.getAuthInstance().currentUser.get();` + * @event google-signin-success + */ + + /** + * Fired when the user is signed-out. + * @event google-signed-out + */ + + /** + * Fired if user requires additional authorization + * @event google-signin-necessary + */ + + /** + * Fired when signed in, and scope has been authorized + * @param {Object} result Authorization result. + * @event google-signin-aware-success + */ + + /** + * Fired when an offline authorization is successful. + * @event google-signin-offline-success + * @param {Object} detail + * @param {string} detail.code The one-time authorization code from Google. + * Your application can exchange this for an `access_token` and `refresh_token` + */ + + /** + * Fired when this scope is not authorized + * @event google-signin-aware-signed-out + */ + properties: { + /** + * App package name for android over-the-air installs. + * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs) + */ + appPackageName: { + type: String, + value: '' + }, + + /** + * The brand being used for logo and styling. + * + * @default 'google' + */ + brand: { + type: String, + value: '' + }, + + /** @private */ + _brand: { + type: String, + computed: '_computeBrand(brand, hasPlusScopes)' + }, + + /** + * a Google Developers clientId reference + */ + clientId: { + type: String, + value: '' + }, + + /** + * The cookie policy defines what URIs have access to the session cookie + * remembering the user's sign-in state. + * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information. + * + * @default 'single_host_origin' + */ + cookiePolicy: { + type: String, + value: '' + }, + + /** + * The height to use for the button. + * + * Available options: short, standard, tall. + * + * @type {HeightValue} + */ + height: { + type: String, + value: 'standard' + }, + + /** + * By default the ripple expands to fill the button. Set this to true to + * constrain the ripple to a circle within the button. + */ + fill: { + type: Boolean, + value: true + }, + + /** + * An optional label for the button for additional permissions. + */ + labelAdditional: { + type: String, + value: 'Additional permissions required' + }, + + /** + * An optional label for the sign-in button. + */ + labelSignin: { + type: String, + value: '' + }, + + _labelSignin: { + type: String, + computed: '_computeSigninLabel(labelSignin, width, _brand)' + }, + + /** + * An optional label for the sign-out button. + */ + labelSignout: { + type: String, + value: 'Sign out' + }, + + /** + * If true, the button will be styled with a shadow. + */ + raised: { + type: Boolean, + value: false + }, + + /** + * The app activity types you want to write on behalf of the user + * (e.g http://schemas.google.com/AddActivity) + */ + requestVisibleActions: { + type: String, + value: '' + }, + + /** + * The Google Apps domain to which users must belong to sign in. + * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information. + */ + hostedDomain: { + type: String, + value: '' + }, + + /** + * Allows for offline `access_token` retrieval during the signin process. + */ + offline: { + type: Boolean, + value: false + }, + + /** + * Forces a re-prompt, even if the user has already granted offline + * access to your application in the past. You only need one of + * `offline` and `offlineAlwaysPrompt`. + */ + offlineAlwaysPrompt: { + type: Boolean, + value: false + }, + + /** + * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive) + * and should be space-delimited. + */ + scopes: { + type: String, + value: '' + }, + + /** + * The theme to use for the button. + * + * Available options: light, dark. + * + * @attribute theme + * @type {ThemeValue} + * @default 'dark' + */ + theme: { + type: String, + value: 'dark' + }, + + /** + * The width to use for the button. + * + * Available options: iconOnly, standard, wide. + * + * @type {WidthValue} + */ + width: { + type: String, + value: 'standard' + }, + + _brandIcon: { + type: String, + computed: '_computeIcon(_brand)' + }, + + /** + * True if *any* element has google+ scopes + */ + hasPlusScopes: { + type: Boolean, + notify: true, + value: false + }, + + /** + * True if additional authorization required globally + */ + needAdditionalAuth: { + type: Boolean, + value: false + }, + + /** + * Is user signed in? + */ + signedIn: { + type: Boolean, + notify: true, + value: false, + observer: '_observeSignedIn' + }, + + /** + * True if authorizations for *this* element have been granted + */ + isAuthorized: { + type: Boolean, + notify: true, + value: false + } + + }, + + _computeButtonClass: function(height, width, theme, signedIn, brand, needAdditionalAuth) { + return "height-" + height + " width-" + width + " theme-" + theme + " signedIn-" + signedIn + " brand-" + brand + " additionalAuth-" + needAdditionalAuth; + }, + + _computeIcon: function(brand) { + return "google:" + brand; + }, + + /* Button state computed */ + _computeButtonIsSignIn: function(signedIn, additionalAuth) { + return !signedIn; + }, + + _computeButtonIsSignOut: function(signedIn, additionalAuth) { + return signedIn && !additionalAuth; + }, + + _computeButtonIsSignOutAddl: function(signedIn, additionalAuth) { + return signedIn && additionalAuth; + }, + + _computeBrand: function(attrBrand, hasPlusScopes) { + var newBrand; + if (attrBrand) { + newBrand = attrBrand; + } else if (hasPlusScopes) { + newBrand = BrandValue.PLUS; + } else { + newBrand = BrandValue.GOOGLE; + }; + return newBrand; + }, + + _observeSignedIn: function(newVal, oldVal) { + if (newVal) { + if (this.needAdditionalAuth) + this.fire('google-signin-necessary'); + this.fire('google-signin-success'); + } + else + this.fire('google-signed-out'); + }, + + /** + * Determines the proper label based on the attributes. + */ + _computeSigninLabel: function(labelSignin, width, _brand) { + if (labelSignin) { + return labelSignin; + } else { + switch(width) { + + case WidthValue.WIDE: + return (_brand == BrandValue.PLUS) ? + LabelValue.WIDE_PLUS : LabelValue.WIDE; + + case WidthValue.STANDARD: + return LabelValue.STANDARD; + + case WidthValue.ICON_ONLY: + return ''; + + default: + console.warn("bad width value: ", width); + return LabelValue.STANDARD; + } + } + }, + + /** Sign in user. Opens the authorization dialog for signing in. + * The dialog will be blocked by a popup blocker unless called inside click handler. + */ + signIn: function () { + this.$.aware.signIn(); + }, + + _signInKeyPress: function (e) { + if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) { + e.preventDefault(); + this.signIn(); + } + }, + + /** Sign out the user */ + signOut: function () { + this.fire('google-signout-attempted'); + this.$.aware.signOut(); + }, + + _signOutKeyPress: function (e) { + if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) { + e.preventDefault(); + this.signOut(); + } + } + }); + }()); +</script> diff --git a/bower_components/google-signin/index.html b/bower_components/google-signin/index.html new file mode 100644 index 0000000..e8f85ae --- /dev/null +++ b/bower_components/google-signin/index.html @@ -0,0 +1,15 @@ +<!doctype html> +<!-- Copyright (c) 2014 Google Inc. All rights reserved. --> +<html> +<head> + + <script src="../webcomponentsjs/webcomponents-lite.js"></script> + <link rel="import" href="../iron-component-page/iron-component-page.html"> + +</head> +<body> + + <iron-component-page></iron-component-page> + +</body> +</html> diff --git a/datalets/graph-datalet/graph-datalet.html b/datalets/graph-datalet/graph-datalet.html index 4c9fbca..985fbcf 100755 --- a/datalets/graph-datalet/graph-datalet.html +++ b/datalets/graph-datalet/graph-datalet.html @@ -246,12 +246,12 @@ Example: return "fill:" + t.color + "; stroke:white" }); + d3.selectAll(".drag").call(force.drag), this.svg.selectAll("g.node").call(this.text); + d3.selectAll('circle').attr("style", function(t){ - return "fill :" + (t.image ? 'url(#image_' + t.id +')' : t.color) + return (t != undefined) ? "fill :" + (t.image ? 'url(#image_' + t.id +')' : t.color) : ""; }); - d3.selectAll(".drag").call(force.drag), this.svg.selectAll("g.node").call(this.text); - }, zoom: function() { diff --git a/datalets/test-google-components/test.html b/datalets/test-google-components/test.html new file mode 100644 index 0000000..1b40081 --- /dev/null +++ b/datalets/test-google-components/test.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Google spreadsheet and document test page</title> + + <script type="text/javascript" src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> + <script type="text/javascript" src="../shared_js/jquery-1.11.2.min.js"></script> + + <link rel="import" href="../../bower_components/google-sheets/google-sheets.html"> + <link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html"> + <link rel="import" href="../../bower_components/iron-icons/iron-icons.html"> + <link rel="import" href="../../bower_components/paper-fab/paper-fab.html"> + <link rel="import" href="../linechart-datalet/linechart-datalet.html"> + + <style> + #sheet{ + height: 400px; + width: 40%; + margin: 40px; + } + + #showVisualization{ + background-color: #2196F3; + color: #ffffff; + margin-left: 50px; + } + + + #datalet_placeholder{ + height: 400px; + width: 40%; + margin: 40px;/ + } + + #doc{ + height: 400px; + width: 100%; + } + </style> + + <script> + + getData = function() + { + var sheet = document.querySelector('google-sheets'); + + sheet.addEventListener('google-sheet-data', function(e) + { + var data = []; + //get the keys + for(var key in this.rows[0]) { + if (key.match(/gsx\$*/)) { + data.push({name: key.replace("gsx$", ""), data: []}); + } + } + //fill the structure with values + for(var row in this.rows){ + var i = 0; + for(var key in this.rows[row]){ + if(key.match(/gsx\$*/)){ + data[i++].data.push(this.rows[row][key].$t.replace("'","")); + } + } + + } + + $("#datalet_placeholder").html('<linechart-datalet ' + + 'data-url="https://docs.google.com/spreadsheets/d/1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" ' + + //'fields=\'[]\'' + + 'title="Google spreadsheet realtime data visualization"' + + 'data=\'' + JSON.stringify(data) + '\'>' + + '</linechart-datalet>'); + + $("#spreadsheet_url").html('<a href="' + this.openInGoogleDocsUrl + '" target="_blank">' + this.openInGoogleDocsUrl + '</a>'); + + }); + + sheet.addEventListener('error', function(e) { + // e.detail.response + }); + + } + + refresh = function(){ + $("#sheet-component").html('<google-sheets key="1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" published></google-sheets>'); + getData(); + } + + $( document ).ready(function() { + getData(); + }); + + </script> + +</head> +<body is="dom-bind"> + <div class="layout vertical"> + <div class="layout vertical"> + <div class="layout horizontal" style="width: 100%;"> + <h1>Google Spreadsheet realtime visualization with Datalet</h1> + <paper-fab id="showVisualization" icon="refresh" onClick="refresh()"></paper-fab> + </div> + <div class="layout horizontal" style="width: 100%;"> + <h3 id="spreadsheet_url"></h3> + </div> + <div class="layout horizontal"> + <iframe id="sheet" src="https://docs.google.com/spreadsheets/d/1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao/edit?usp=sharing?embedded=true"></iframe> + <div id="datalet_placeholder"></div> + </div> + </div> + + <div class="layout vertical"> + <h1 style="width: 100%;margin-top: 100px;">Google Docs realtime visualization with Datalet</h1> + <iframe id="doc" src="https://docs.google.com/document/d/1IIfmqTGBpTkEMHPnuxJeoOJI3u-8wQu9dkf49X2T6SE/edit?usp=sharing?embedded=true"></iframe> + </div> + </div> + + <div id="sheet-component"> + <google-sheets key="1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" published></google-sheets> + </div> + +</body> +</html> \ No newline at end of file