Added more material design touch response, swipe pages, and began effects lists

This commit is contained in:
Thaum
2014-12-09 13:42:05 +00:00
parent ad474590bd
commit a26589157e
33 changed files with 840 additions and 63 deletions

View File

@@ -1,3 +1,14 @@
TODO
====
Strip the core interaction out of swipe-pages to create an element that just listens to swipe events
wishlist:
* swipes are emitted as an event if possible
* swiping should translate the element
* it should bounce back to its resting position when released
Packages used
=============

View File

@@ -15,5 +15,3 @@ cw4gn3r:jquery-event-drag
underscore
aldeed:collection2
aldeed:autoform
multiply:iron-router-progress

View File

@@ -14,7 +14,6 @@ blaze@2.0.3
boilerplate-generator@1.0.1
callback-hook@1.0.1
check@1.0.2
coffeescript@1.0.4
ctl-helper@1.0.4
ctl@1.0.2
cw4gn3r:jquery-event-drag@2.2.0
@@ -54,7 +53,6 @@ mobile-status-bar@1.0.1
mongo-livedata@1.0.6
mongo@1.0.8
mrt:moment@2.6.0
multiply:iron-router-progress@1.0.0
npm-bcrypt@0.7.7
observe-sequence@1.0.3
ordered-dict@1.0.1

View File

@@ -30,7 +30,7 @@ Schemas.Effect = new SimpleSchema({
//indicates what the effect originated from
type: {
type: String,
defaultValue: "default",
allowedValues: ["default", "inate", "class", "race", "feat", "equippedMagic", "equippedMundane", "external"]
defaultValue: "editable",
allowedValues: ["editable", "feat", "buff", "equipment", "inate"]
}
});

View File

@@ -0,0 +1,3 @@
Template.registerHelper("session", function(key){
return Session.get(key);
});

View File

@@ -0,0 +1,3 @@
Template.registerHelper("signedString", function(number){
return number > 0? "+" + number : "" + number;
});

View File

@@ -4,70 +4,72 @@
{{> abilityCards}}
</div>
<div class="statsFlex">
<paper-shadow class="card" id="armor">
{{#clickCard class="card" id="armor"}}
<h1>{{attributeValue "armor"}}</h1>
<p class="caption">Armor</p>
</paper-shadow>
<paper-shadow class="card" id="initiative">
{{/clickCard}}
{{#clickCard class="card" id="initiative"}}
<h1>{{skillMod "initiative"}}</h1>
<p class="caption">Initiative</p>
</paper-shadow>
<paper-shadow class="card" id="proficiencyBonus">
{{/clickCard}}
{{#clickCard class="card" id="proficiencyBonus"}}
<h1>{{attributeValue "proficiencyBonus"}}</h1>
<p class="caption">Proficiency Bonus</p>
</paper-shadow>
<paper-shadow class="card" id="speed">
{{/clickCard}}
{{#clickCard class="card" id="speed"}}
<h1>{{attributeValue "speed"}}</h1>
<p class="caption">Speed</p>
</paper-shadow>
<paper-shadow class="card" id="passivePerception">
{{/clickCard}}
{{#clickCard class="card" id="passivePerception"}}
<h1>{{passiveSkill "perception"}}</h1>
<p class="caption">Passive Perception</p>
</paper-shadow>
<paper-shadow class="card" id="hitDice">
<h1>{{> hitDice hitDice="d6HitDice" d="6"}}</h1>
<h1>{{> hitDice hitDice="d8HitDice" d="8"}}</h1>
<h1>{{> hitDice hitDice="d10HitDice" d="10"}}</h1>
<h1>{{> hitDice hitDice="d12HitDice" d="12"}}</h1>
{{/clickCard}}
{{#clickCard class="card" id="hitDice"}}
<h1 class="attribute">{{> hitDice hitDice="d6HitDice" d="6"}}</h1>
<h1 class="attribute">{{> hitDice hitDice="d8HitDice" d="8"}}</h1>
<h1 class="attribute">{{> hitDice hitDice="d10HitDice" d="10"}}</h1>
<h1 class="attribute">{{> hitDice hitDice="d12HitDice" d="12"}}</h1>
<p class="caption">Hit Dice</p>
</paper-shadow>
{{/clickCard}}
{{# if canCast}}
<paper-shadow class="card" id="spellSlots">
<h1>{{> spellSlots}}</h1>
<p class="caption">Spell Slots</p>
</paper-shadow>
{{#clickCard class="card" id="spellSlots"}}
<h1>{{> spellSlots}}</h1>
<p class="caption">Spell Slots</p>
{{/clickCard}}
{{/if}}
{{# if attributeBase "rages"}}
<paper-shadow class="card" id="rages">
<h1>{{attributeValue "rages"}}</h1>
<p class="caption">rages</p>
</paper-shadow>
{{#clickCard class="card" id="rages"}}
<h1>{{attributeValue "rages"}}</h1>
<p class="caption">rages</p>
{{/clickCard}}
{{/if}}
{{# if attributeBase "sorceryPoints"}}
<paper-shadow class="card" id="sorceryPoints">
<h1>{{attributeValue "sorceryPoints"}}</h1>
<p class="caption">Sorcery Points</p>
</paper-shadow>
{{#clickCard class="card" id="sorceryPoints"}}
<h1>{{attributeValue "sorceryPoints"}}</h1>
<p class="caption">Sorcery Points</p>
{{/clickCard}}
{{/if}}
{{# if attributeBase "expertiseDice"}}
<paper-shadow class="card" id="expertiseDice">
<h1>{{attributeValue "expertiseDice"}}</h1>
<p class="caption">Expertise Dice</p>
</paper-shadow>
{{#clickCard class="card" id="expertiseDice"}}
<h1>{{attributeValue "expertiseDice"}}</h1>
<p class="caption">Expertise Dice</p>
{{/clickCard}}
{{/if}}
{{# if attributeBase "superiorityDice"}}
<paper-shadow class="card" id="superiorityDice">
<h1>{{attributeValue "superiorityDice"}}</h1>
<p class="caption">Superiority Dice</p>
</paper-shadow>
{{#clickCard class="card" id="superiorityDice"}}
<h1>{{attributeValue "superiorityDice"}}</h1>
<p class="caption">Superiority Dice</p>
{{/clickCard}}
{{/if}}
</div>
</div>
{{> attributeDialog character=this}}
{{> skillDialog character=this}}
</template>
<template name="hitDice">
{{# if ../attributeBase hitDice}}
{{../attributeValue hitDice}}d{{d}} + {{../abilityMod "constitution"}}
{{../attributeValue hitDice}}d{{d}} + {{../abilityMod "constitution"}}
{{/if}}
</template>
@@ -82,3 +84,59 @@
{{attributeValue "level8SpellSlots"}}
{{attributeValue "level9SpellSlots"}}
</template>
<template name="attributeDialog">
<!--needs character, attributeName, attributeTitle-->
<paper-dialog id="attributeDialog" heading={{session "selectedAttributeTitle"}} backdrop transition="core-transition-center">
{{#if attributeName}}
{{character.attributeValue attributeName}}<br>
{{#each effects.add}}
{{> attributeEffect}}
{{/each}}
{{#each effects.mul}}
{{> attributeEffect}}
{{/each}}
{{#each effects.min}}
{{> attributeEffect}}
{{/each}}
{{#each effects.max}}
{{> attributeEffect}}
{{/each}}
{{signedString attribute.adjustment}}
{{/if}}
</paper-dialog>
</template>
<template name="attributeEffect">
{{#if editing}}
<div class="editEffect">
<paper-dropdown-menu label="Operation">
<core-menu id="operationSelector">
<paper-item>add</paper-item>
<paper-item>multiply</paper-item>
<paper-item>min</paper-item>
<paper-item>max</paper-item>
</core-menu>
</paper-dropdown-menu>
<paper-input id="effectValueInput" label="Value" floatinglabel></paper-input>
<paper-input id="effectNameInput" label="Name" floatinglabel></paper-input>
<paper-icon-button id="doneButton" icon="done" title="save" role="button" aria-label="save"></paper-icon-button>
<paper-icon-button id="cancelButton" icon="clear" title="cancel" role="button" aria-label="cancel"></paper-icon-button>
<paper-icon-button id="deleteButton" icon="delete" title="delete" role="button" aria-label="delete"></paper-icon-button>
</div>
{{else}}
<div class="effect">
<div class="effectValue">{{operation}} {{signedEffectValue}}</div><div class="effectName"> {{name}}</div>
{{#if editable}}
<div class="editButton">EDIT</div>
{{/if}}
</div>
{{/if}}
</template>
<template name="skillDialog">
<!--needs character and skill string-->
<paper-dialog id="skillDialog" backdrop transition="core-transition-center">
</paper-dialog>
</template>

View File

@@ -0,0 +1,91 @@
selectAttribute = function(name, title){
Session.set("selectedAttribute", name);
Session.set("selectedAttributeTitle", title);
Session.set("editingEffect", null);
document.querySelector("#attributeDialog").toggle();
};
selectSkill = function(name, title){
Session.set("selectedSkill", name);
Session.set("selectedSkillTitle", title);
Session.set("editingEffect", null);
document.querySelector("#skillDialog").toggle();
};
Template.stats.events({
"click #armor": function(){
console.log("clicked armor");
selectAttribute("armor", "Armor")
}
});
Template.attributeDialog.helpers({
attributeTitle: function(){
return Session.get("selectedAttributeTitle");
},
attributeName: function(){
return Session.get("selectedAttribute");
},
attribute: function(){
return this.character.getField(Session.get("selectedAttribute"));
},
effects: function(){
var attribute = this.character.getField(Session.get("selectedAttribute"));
return _.groupBy(attribute.effects, "operation");
},
effectValue: function(){
return evaluateEffect(Template.parentData(1).character._id, this);
}
});
Template.attributeEffect.helpers({
editing: function(){
return Session.get("editingEffect") === this._id;
},
editable: function(){
return this.type === "editable";
},
operation: function(){
switch(this.operation){
case "add":
return;
case "mul":
return Spacebars.SafeString("&times;");
case "min":
return "min";
case "max":
return "max";
default:
return this.operation;
}
},
signedEffectValue: function(){
var value = evaluateEffect(Template.parentData(1).character._id, this);
return signedString(value);
}
});
Template.attributeEffect.events({
"click .editButton": function(event){
Session.set("editingEffect", this._id);
},
"click #doneButton": function(event){
var newEffect = {};
//TODO setup the changed effect
var attribute = Session.get("selectedAttribute");
var charId = Template.parentData(2)._id;
Meteor.call("updateEffect", charId, attribute, this._id, newEffect)
Session.set("editingEffect", null);
},
"click #cancelButton": function(event){
Session.set("editingEffect", null);
},
"click #deleteButton": function(event){
console.log("check that ", Template.parentData(2), "is a character");
var attribute = Session.get("selectedAttribute");
var pullObject = {};
pullObject[attribute + ".effects"] = {_id: this._id};
Characters.update(Template.parentData(2)._id, {$pull: pullObject});
Session.set("editingEffect", null);
}
});

View File

@@ -11,6 +11,7 @@
text-align: center;
background-color: #D50000;
padding: 16px;
position: relative;
}
.abilityFlex .card {

View File

@@ -10,6 +10,7 @@
<template name="strengthCard">
<paper-shadow class="card double">
<div class="abilityScore red white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "strength"}}</h1>
<h2>{{abilityMod "strength"}}</h2>
</div>
@@ -25,6 +26,7 @@
<template name="dexterityCard">
<paper-shadow class="card double">
<div class="abilityScore green white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "dexterity"}}</h1>
<h2>{{abilityMod "dexterity"}}</h2>
</div>
@@ -42,6 +44,7 @@
<template name="constitutionCard">
<paper-shadow class="card double">
<div class="abilityScore deep-orange white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "constitution"}}</h1>
<h2>{{abilityMod "constitution"}}</h2>
</div>
@@ -56,6 +59,7 @@
<template name="intelligenceCard">
<paper-shadow class="card double">
<div class="abilityScore indigo white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "intelligence"}}</h1>
<h2>{{abilityMod "intelligence"}}</h2>
</div>
@@ -75,6 +79,7 @@
<template name="wisdomCard">
<paper-shadow class="card double">
<div class="abilityScore purple white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "wisdom"}}</h1>
<h2>{{abilityMod "wisdom"}}</h2>
</div>
@@ -94,6 +99,7 @@
<template name="charismaCard">
<paper-shadow class="card double">
<div class="abilityScore pink white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "charisma"}}</h1>
<h2>{{abilityMod "charisma"}}</h2>
</div>

View File

@@ -3,17 +3,18 @@
<paper-tab>Stats</paper-tab>
<paper-tab>Features</paper-tab>
<paper-tab>Inventory</paper-tab>
Proficiency Bonus
<paper-tab>Spellbook</paper-tab>
<paper-tab>Persona</paper-tab>
<paper-tab>Journal</paper-tab>
</paper-tabs>
<core-animated-pages selected={{selectedTab}} transitions="slide-from-right">
<div>{{> stats}}</div>
<div>features</div>
<div>inventory</div>
<div>spellBook</div>
<div>persona</div>
<div>journal</div>
<core-animated-pages id="tabPages" selected={{selectedTab}} transitions="slide-from-right">
<swipe-detect>{{> stats}}</swipe-detect>
<swipe-detect>features</swipe-detect>
<swipe-detect>inventory</swipe-detect>
<swipe-detect>spellBook</swipe-detect>
<swipe-detect>persona</swipe-detect>
<swipe-detect>journal</swipe-detect>
</core-animated-pages>
</template>

View File

@@ -1,6 +1,18 @@
Template.characterSheet.created = function(){
Template.instance().selectedTab = new ReactiveVar(0)
}
};
var setTab = function(instance, num){
instance.selectedTab.set(num);
};
var incTab = function(instance, num){
var current = +instance.selectedTab.get();
var selected = current + num;
if (selected < 0) return;
if (selected >= document.querySelector('#tabPages').children.length) return;
setTab(instance, selected);
};
Template.characterSheet.rendered = function(){
var observer = new ObjectObserver(document.querySelector('#characterSheetTabs'));
@@ -9,11 +21,11 @@ Template.characterSheet.rendered = function(){
Object.keys(changed).forEach(function(property) {
if(property === "selected"){
var selected = changed[property];
instance.selectedTab.set(selected);
setTab(instance, selected);
}
})
});
}
};
Template.characterSheet.helpers({
selectedTab: function(){
@@ -22,8 +34,16 @@ Template.characterSheet.helpers({
});
Template.characterSheet.events({
})
"#tabPages track": function(event){
console.log(event);
},
"swipeleft": function(event){
incTab(Template.instance(), 1);
},
"swiperight": function(event){
incTab(Template.instance(), -1);
},
});
/* requires the following templates
stats

View File

@@ -2,6 +2,11 @@
height: 32px;
display: flex;
align-items: center;
position: relative;
}
.skillRow:hover{
background: #FFEBEE;
}
.profIcon{

View File

@@ -15,6 +15,7 @@
<template name="skillRow">
<div class="subhead skillRow">
{{> ripple}}
<div class="profIcon" style="background-image: url(/png/profIcons/{{profIcon skill}})"></div>
{{#if failSkill}}
<div class="fail skillMod">fail</div>

View File

@@ -1,11 +1,16 @@
<head>
<title>RPG Docs</title>
<meta name="viewport" content="width=device-width initial-scale=1.0, user-scalable=no">
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="/bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="/bower_components/core-animated-pages/core-animated-pages.html">
<link rel="import" href="/bower_components/core-animated-pages/transitions/slide-from-right.html">
<link rel="import" href="/bower_components/paper-shadow/paper-shadow.html">
<link rel="import" href="/bower_components/paper-spinner/paper-spinner.html">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,500,500italic,700,700italic,900,900italic,100italic,100&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="/bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="/bower_components/core-animated-pages/core-animated-pages.html">
<link rel="import" href="/bower_components/core-animated-pages/transitions/slide-from-right.html">
<link rel="import" href="/bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="/bower_components/paper-shadow/paper-shadow.html">
<link rel="import" href="/bower_components/paper-spinner/paper-spinner.html">
<link rel="import" href="/bower_components/paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="/bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="/bower_components/paper-input/paper-input.html">
<link rel="import" href="/custom_components/swipe-detect/swipe-detect.html">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,500,500italic,700,700italic,900,900italic,100italic,100&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
</head>

View File

@@ -0,0 +1,6 @@
<template name="clickCard">
<paper-shadow class="clickCard {{class}}" z="1" animated id={{id}}>
{{> ripple}}
{{> UI.contentBlock}}
</paper-shadow>
</template>

View File

@@ -0,0 +1,8 @@
Template.clickCard.events({
"click paper-shadow ": function(event){
event.currentTarget.setZ(2);
_.delay(function(){
event.currentTarget.setZ(1);
}, 300)
}
})

View File

@@ -0,0 +1,8 @@
.custom-ripple {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
color: #D50000;
}

View File

@@ -0,0 +1,7 @@
<template name="ripple">
{{#if color}}
<paper-ripple style="color:{{color}}" class="recenteringTouch custom-ripple"></paper-ripple>
{{else}}
<paper-ripple class="recenteringTouch custom-ripple"></paper-ripple>
{{/if}}
</template>

View File

@@ -0,0 +1,12 @@
Meteor.methods({
updateEffect: function (charId, attributeName, effectId, newEffect) {
var selector = {_id: charId};
selector[attributeName + ".effects._id"] = effectId;
var setter = {};
setter[attributeName + ".effects.$"] = newEffect
Characters.update(
selector,
{ $set: setter }
)
}
});

View File

@@ -17,3 +17,13 @@
=> Started your app.
=> App running at: http://localhost:3000/
Can't listen on port 3000. Perhaps another Meteor is running?
Running two copies of Meteor in the same application directory
will not work. If something else is using port 3000, you can
specify an alternative port with --port <port>.
Can't listen on port 3000. Perhaps another Meteor is running?
Running two copies of Meteor in the same application directory
will not work. If something else is using port 3000, you can
specify an alternative port with --port <port>.

View File

@@ -0,0 +1,3 @@
{
"directory": "../"
}

View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,6 @@
node_modules
dist
.sass-cache
.tmp
.editorconfig
.jshintrc

View File

@@ -0,0 +1,35 @@
swipe-pages
================
See the [component page](http://TheSeamau5.github.io/swipe-pages) for more information.
## TODO
- [x] Improve page scroll stability
- [x] Improve page scroll performance
- [ ] Improve performance in swiping between pages (by reusing pages?)
- [x] Have pages scroll down independently of each other
- [ ] Make element play nicer with the polymer core-elements (like core-scroll-header-panel and core-list)
- [ ] Add option to mark each page with a browser tag to resume state from url
- [x] Include sane defaults for hardware acceleration (translateZ hack in the right places)
- [ ] Make a nicer demo with more features to better explain the element
- [ ] Add option to reverse direction for rtl languages
- [ ] Do some more rigorous testing to ensure stability!!!
- [ ] Allow for individual pages to be created dynamically.
## Installation
With Bower:
bower install swipe-pages
## Basic Example
<swipe-pages>
<swipe-page>I am page 0<swipe-page>
<swipe-page>I am page 1<swipe-page>
<swipe-page>I am page 2<swipe-page>
</swipe-pages>

View File

@@ -0,0 +1,16 @@
{
"name": "swipe-pages",
"version": "0.2.1",
"keywords": [
"seed",
"polymer",
"web-components"
],
"main": "swipe-pages.html",
"dependencies": {
"polymer": "Polymer/polymer#^0.4.0"
},
"devDependencies": {
"polymer-test-tools": "Polymer/polymer-test-tools#^0.4.0"
}
}

View File

@@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=no">
<title>swipe-pages Demo</title>
<script src="../platform/platform.js"></script>
<link rel="import" href="swipe-pages.html">
</head>
<body unresolved fullbleed layout vertical center center-justified>
<swipe-pages>
<swipe-page style="background-color: blue" layout vertical>
<div style="top: 50%; height: 100%; width: 100%; background-color:beige"></div>
</swipe-page>
<swipe-page style="background-color: red"></swipe-page>
<swipe-page style="background-color: green"></swipe-page>
<swipe-page style="background-color: indigo"></swipe-page>
</swipe-pages>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<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/polymer.html">
<link rel="import" href="../core-component-page/core-component-page.html">
</head>
<body unresolved>
<core-component-page></core-component-page>
</body>
</html>

View File

@@ -0,0 +1,15 @@
:host, .pageContainer{
display: block;
height: 100%;
width: 100%;
}
.pageContainer{
overflow-y: auto;
background-color: rgba(255,255,255,0);
/*This is for a bug in chrome where pages flash white. Why? I dunno.
But, these two lines solve the problem... so... DO NOT TOUCH THIS!*/
backface-visibility: hidden;
transform: scale(1);
}

View File

@@ -0,0 +1,30 @@
<link rel="import" href="../polymer/polymer.html">
<polymer-element name="swipe-page" attributes="">
<template>
<link rel="stylesheet" href="swipe-page.css" />
<div id="pageContainer" class="pageContainer">
<content></content>
</div>
</template>
<script>
(function(){
'use strict';
Polymer({
});
})();
</script>
</polymer-element>

View File

@@ -0,0 +1,20 @@
:host, .pagesContainer {
display: block;
height: 100%;
width: 100%;
}
:host{
overflow: hidden;
}
.pagesContainer{
overflow-x : hidden;
overflow-y: hidden;
background-color: rgba(255,255,255,0);
transform: translateZ(0);
}
content[select="swipe-page"]::content *{
position: relative;
}

View File

@@ -0,0 +1,279 @@
<!--
Copyright (c) 2014 Hassan Hayat <hassan.hayat7@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!--
Provides horizontally swipeable pages.
The `swipe-pages` element must have `swipe-page` elements as its children.
This ensures readibility of code and a given amount of control on the behavior
of individual pages.
A `swipe-pages` element is an element containing several `swipe-page` elements
as subviews. The `swipe-pages` element is given a certain width and height
through CSS and then each individual `swipe-page` will automatically take of the full size
of the parent element. This means that the `swipe-page` elements are assumed
to all have the exact same size which they all derive from the `swipe-pages`
element.
###Example:
<swipe-pages selected = "1">
<swipe-page>Hey I'm page 0</swipe-page>
<swipe-page><h1>Hi, I'm on page 1</h1></swipe-page>
<swipe-page><p>I am page 2 and I totally rock!</p></swipe-page>
</swipe-pages>
Swiping left moves to the next page while swiping right moves to the previous page.
This behavior is very typical on mobile applications.
The key to this element is that when swiping, the page follows your finger
horizontally so as to give the user immediate feedback that he/she is swiping
between pages.
Pages only transition when the swipe gesture has crossed a certain threshold
which is exposed by the `threshold` attribute.
###Example:
<swipe-pages threshold = "0.5">
<swipe-page> ... </swipe-page>
<swipe-page> ... </swipe-page>
</swipe-pages>
By setting the `threshold` to 0.5, you ensure that the page will only transition
if the swipe gesture has crossed half the `swipe-pages` width horizontally.
`threshold` accepts values between 0 and 1.
A `threshold` value of 0 implies that any swipe gesture will cause a page
transition. A `threshold` value of 1 implies that no page transition is possible
as you must cross more that the entire size of the `swipe-pages` element horizontally
which is impossible given that the size of the `swipe-pages` element is well-defined.
@class swipe-pages
@blurb Provides horizontally swipeable pages.
@status alpha
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="swipe-page.html">
<polymer-element name="swipe-pages" attributes="selected threshold transitionDuration">
<template>
<link rel="stylesheet" href="swipe-pages.css" />
<div id="pagesContainer" class="pagesContainer" touch-action="pan-y">
<content id="pages" select="swipe-page"></content>
</div>
</template>
<script>
(function(){
'use strict';
var isWebkit = document.body.style.webkitTransform !== undefined;
var getPositionArray = function(length){
var a = [];
for (var i = 0; i < length; i++){
a[i] = (i * 100);
}
return a;
};
var resetAnimations = function(element){
setTransition("", element);
};
var setAnimationDuration = function(duration, element, easingFunction){
easingFunction = easingFunction || "ease-out";
var transition = (isWebkit ? "-webkit-" : "") + "transform " + duration.toString() + "s " + easingFunction;
setTransition(transition, element);
};
var setTransition = function(transition, element){
if (isWebkit){
element.$.pagesContainer.style.webkitTransition = transition;
}else{
element.$.pagesContainer.style.transition = transition;
}
};
var setTransform = function(transform, element){
if (isWebkit){
element.$.pagesContainer.style.webkitTransform = transform;
}else{
element.$.pagesContainer.style.transform = transform;
}
};
var moveToPosition = function(position, element){
var transform = "translateX(" + position.toString() + "%)";
setTransform(transform, element);
};
var moveToPage = function(pageNumber, element){
var position = -pageNumber * 100 / element.pageCount;
moveToPosition(position, element);
};
var resetScrollTop = function(element){
element.$.pageContainer.scrollTop = 0;
};
Polymer({
get pageCount(){
return this.$.pages.getDistributedNodes().length;
},
get pages(){
return this.$.pages.getDistributedNodes();
},
get pageWidth(){
return this.getBoundingClientRect().width;
},
resetPositions : function(){
var positionArray = getPositionArray(this.pageCount);
for (var i = 0; i < this.pageCount; i++){
this.pages[i].style.left = "" + (positionArray[i] / this.pageCount) + "%";
this.pages[i].style.top = "-" + positionArray[i].toString() + "%";
}
},
resetWidths : function(){
this.$.pagesContainer.style.width = "" + (100 * this.pageCount) + "%";
for (var i = 0; i < this.pageCount; i++){
this.pages[i].style.width = "" + (100 / this.pageCount) + "%";
}
},
setupEventHandlers : function(){
this.setupTrackStartEventHandler();
this.setupTrackEventHandler();
this.setupTrackEndEventHandler();
},
setupTrackStartEventHandler : function(){
PolymerGestures.addEventListener(this, "trackstart", function(event){
resetAnimations(this);
});
},
setupTrackEventHandler : function(){
PolymerGestures.addEventListener(this, "track", function(event){
var isFirstPage = (this.selected === 0);
var isLastPage = (this.selected === (this.pageCount - 1));
var userIsSwipingLeftwards = (event.dx < 0);
var userIsSwipingRightwards = (event.dx > 0);
var tryingToSwipeToLeftOfFirstPage = userIsSwipingRightwards && isFirstPage;
var tryingToSwipeToRightOfLastPage = userIsSwipingLeftwards && isLastPage;
var tryingToSwipeToOutOfBoundsPage = tryingToSwipeToLeftOfFirstPage || tryingToSwipeToRightOfLastPage;
if (!tryingToSwipeToOutOfBoundsPage){
var position = -(this.selected - (event.dx / this.pageWidth)) * 100 / this.pageCount;
moveToPosition(position, this);
}
});
},
setupTrackEndEventHandler : function(){
PolymerGestures.addEventListener(this, "trackend", function(event){
var userIsSwipingLeftwards = (event.dx < 0);
var userIsSwipingRightwards = (event.dx > 0);
var thresholdWasCrossed = (Math.abs(event.dx) / this.pageWidth) > this.threshold;
setAnimationDuration(this.transitionDuration, this);
if (thresholdWasCrossed){
if (userIsSwipingRightwards){
this.selected = Math.max(this.selected - 1, 0);
}
if (userIsSwipingLeftwards){
this.selected = Math.min(this.selected + 1, this.pageCount - 1);
}
}else{
moveToPage(this.selected, this);
}
});
},
publish: {
/**
* Specifies the index of the currently selected page
* @attribute selected
* @default 0
* @type Number
*/
threshold: 0.3,
/**
* Specifies the threshold the user must swipe in order to
* cause a page transition.
* Only accepts values between 0 and 1.
* @attribute threshold
* @default 0.3
* @type Number
*/
transitionDuration: 0.3,
/**
* Specifies the duration (in seconds) of the transition from one
* page to another
* @attribute transitionDuration
* @default 0.3
* @type Number
*/
selected: 0,
},
selectedChanged : function(oldValue, newValue){
if (newValue >= this.pageCount || newValue < 0){
var errorMessage = "Page " + newValue.toString() + " is not a defined page. There are only " + this.pageCount.toString() + " pages.";
throw errorMessage;
}
moveToPage(newValue, this);
var self = this;
window.setTimeout(function(){
resetScrollTop(self.pages[oldValue]);
}, self.transitionDuration * 1000);
},
ready: function(){
this.resetPositions();
this.resetWidths();
this.setupEventHandlers();
}
});
})();
</script>
</polymer-element>

View File

@@ -0,0 +1,83 @@
<link rel="import" href="/bower_components/polymer/polymer.html">
<polymer-element name="swipe-detect" attributes="threshold">
<template>
<div id="swipeDetector" class="swipeDetector" touch-action="pan-y">
<content></content>
</div>
</template>
<script>
(function(){
'use strict';
var isWebkit = document.body.style.webkitTransform !== undefined;
Polymer({
setupEventHandlers : function(){
this.setupTrackStartEventHandler();
this.setupTrackEventHandler();
this.setupTrackEndEventHandler();
},
setupTrackStartEventHandler : function(){
PolymerGestures.addEventListener(this, "trackstart", function(event){
//tracking started
});
},
setupTrackEventHandler : function(){
PolymerGestures.addEventListener(this, "track", function(event){
var userIsSwipingLeftwards = (event.dx < 0);
var userIsSwipingRightwards = (event.dx > 0);
//tracking events fired
});
},
setupTrackEndEventHandler : function(){
PolymerGestures.addEventListener(this, "trackend", function(event){
var userIsSwipingLeftwards = (event.dx < 0);
var userIsSwipingRightwards = (event.dx > 0);
var thresholdWasCrossed = (Math.abs(event.dx) / this.getBoundingClientRect().width) > this.threshold;
if (thresholdWasCrossed){
if (userIsSwipingRightwards){
$(this).trigger("swiperight")
}
if (userIsSwipingLeftwards){
$(this).trigger("swipeleft")
}
}
});
},
publish: {
/**
* Specifies the threshold the user must swipe in order to
* cause a page transition.
* Only accepts values between 0 and 1.
* @attribute threshold
* @default 0.3
* @type Number
*/
threshold: 0.3,
},
ready: function(){
this.setupEventHandlers();
}
});
})();
</script>
</polymer-element>