From 5ef91d53540228c6dc5961546fcb8ad795d2c686 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Wed, 15 Jul 2015 21:50:49 +0200 Subject: [PATCH 1/4] Convert progress-linear from ember-material-design repo. Props to mike1o1 --- addon/components/paper-progress-linear.js | 80 ++++++ app/components/paper-progress-linear.js | 1 + app/styles/ember-paper.scss | 1 + app/styles/paper-progress-linear.scss | 235 ++++++++++++++++++ .../components/paper-progress-linear.hbs | 5 + .../dummy/app/controllers/progress-linear.js | 34 +++ tests/dummy/app/router.js | 1 + tests/dummy/app/templates/application.hbs | 1 + tests/dummy/app/templates/progress-linear.hbs | 35 +++ .../components/paper-progress-linear-test.js | 69 +++++ 10 files changed, 462 insertions(+) create mode 100644 addon/components/paper-progress-linear.js create mode 100644 app/components/paper-progress-linear.js create mode 100644 app/styles/paper-progress-linear.scss create mode 100644 app/templates/components/paper-progress-linear.hbs create mode 100644 tests/dummy/app/controllers/progress-linear.js create mode 100644 tests/dummy/app/templates/progress-linear.hbs create mode 100644 tests/unit/components/paper-progress-linear-test.js diff --git a/addon/components/paper-progress-linear.js b/addon/components/paper-progress-linear.js new file mode 100644 index 000000000..8904b0c4d --- /dev/null +++ b/addon/components/paper-progress-linear.js @@ -0,0 +1,80 @@ +import Ember from 'ember'; + +function makeTransform(value) { + var scale = value / 100; + var translateX = (value - 100) / 2; + return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)'; +} + +export default Ember.Component.extend({ + tagName: 'md-progress-linear', + + classNames: ['md-default-theme'], + + isInserted: false, + + init() { + this._super(...arguments); + this.setupTransforms(); + }, + + didInsertElement() { + this._super(...arguments); + + this.set('isInserted', true); + this.$('.md-container').addClass('md-ready'); + }, + + constants: Ember.inject.service('constants'), + + attributeBindings: ['md-mode', 'md-buffer-value'], + + transforms: new Array(101), + + setupTransforms() { + for (var i = 0; i < 101; i++) { + this.transforms[i] = makeTransform(i); + } + }, + + bar1Style: Ember.computed('clampedBufferValue', function() { + return new Ember.Handlebars.SafeString('transform: ' + this.transforms[this.get('clampedBufferValue')]); + }), + + bar2Style: Ember.computed('clampedValue', function() { + + if (this.get('md-mode') === 'query') { + return new Ember.Handlebars.SafeString(''); + } + + return new Ember.Handlebars.SafeString('transform: ' + this.transforms[this.get('clampedValue')]); + }), + + clampedValue: Ember.computed('value', function() { + + var value = this.get('value'); + if (value > 100) { + return 100; + } + + if (value < 0) { + return 0; + } + + return Math.ceil(value || 0); + }), + + clampedBufferValue: Ember.computed('md-buffer-value', function() { + var value = this.get('md-buffer-value'); + if (value > 100) { + return 100; + } + + if (value < 0) { + return 0; + } + + return Math.ceil(value || 0); + }) + +}); diff --git a/app/components/paper-progress-linear.js b/app/components/paper-progress-linear.js new file mode 100644 index 000000000..6de13e16a --- /dev/null +++ b/app/components/paper-progress-linear.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-progress-linear'; \ No newline at end of file diff --git a/app/styles/ember-paper.scss b/app/styles/ember-paper.scss index 893dd81ac..59cd44db8 100644 --- a/app/styles/ember-paper.scss +++ b/app/styles/ember-paper.scss @@ -75,6 +75,7 @@ @import 'paper-icon'; @import 'paper-slider'; @import 'paper-subheader'; +@import 'paper-progress-linear'; @import 'paper-sidenav'; @import 'paper-backdrop'; diff --git a/app/styles/paper-progress-linear.scss b/app/styles/paper-progress-linear.scss new file mode 100644 index 000000000..4e69ac905 --- /dev/null +++ b/app/styles/paper-progress-linear.scss @@ -0,0 +1,235 @@ +$progress-linear-bar-height: 5px !default; + +md-progress-linear:not([md-mode="indeterminate"]) { + display: block; + width: 100%; + height: $progress-linear-bar-height; + + .md-container { + overflow: hidden; + position: relative; + height: $progress-linear-bar-height; + top: $progress-linear-bar-height; + transform: translate(0, 5px) scale(1, 0); + transition: all .3s linear; + } + + .md-container.md-ready { + transform: translate(0, 0) scale(1, 1); + } + + .md-bar { + height: $progress-linear-bar-height; + position: absolute; + width: 100%; + } + + .md-bar1, .md-bar2 { + transition: all 0.2s linear; + } + + &[md-mode=determinate] { + .md-bar1 { + display: none; + } + } + + &[md-mode=buffer] { + .md-container { + background-color: transparent !important; + } + + .md-dashed:before { + content: ""; + display: block; + height: $progress-linear-bar-height; + width: 100%; + margin-top: 0; + position: absolute; + background-color: transparent; + background-size: 10px 10px !important; + background-position: 0px -23px; + animation: buffer 3s infinite linear; + } + } + + &[md-mode=query] { + .md-bar2 { + animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000); + } + } +} + +md-progress-linear[md-mode="indeterminate"] { + display: block; + width: 100%; + height: $progress-linear-bar-height; + position: relative; + .md-container { + width: 100%; + overflow: hidden; + position: relative; + height: $progress-linear-bar-height; + top: $progress-linear-bar-height; + transition: all .3s linear; + .md-bar { + height: $progress-linear-bar-height; + left: 0; + width: 288 * 100% / 360; + position: absolute; + top: 0; + bottom: 0; + } + .md-bar1 { + animation: md-progress-linear-indeterminate-scale-1 4s infinite, + md-progress-linear-indeterminate-1 4s infinite; + } + .md-bar2 { + animation: md-progress-linear-indeterminate-scale-2 4s infinite, + md-progress-linear-indeterminate-2 4s infinite; + } + } +} + +@keyframes query { + 0% { + opacity: 1; + transform: translateX(35%) scale(.3, 1); + } + 100% { + opacity: 0; + transform: translateX(-50%) scale(0, 1); + } +} + +@keyframes buffer { + 0% { + opacity: 1; + background-position: 0px -23px; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + background-position: -200px -23px; + } +} + +@keyframes md-progress-linear-indeterminate-scale-1 { + 0% { + transform: scaleX(0.1); + animation-timing-function: linear; + } + 36.6% { + transform: scaleX(0.1); + animation-timing-function: cubic-bezier(0.334731432, 0.124819821, 0.785843996, 1); + } + 69.15% { + transform: scaleX(0.83); + animation-timing-function: cubic-bezier(0.225732004, 0, 0.233648906, 1.3709798); + } + 100% { + transform: scaleX(0.1); + } +} + +@keyframes md-progress-linear-indeterminate-1 { + 0% { + left: -378.6 * 100% / 360; + animation-timing-function: linear; + } + 20% { + left: -378.6 * 100% / 360; + animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495818703); + } + 69.15% { + left: 77.4 * 100% / 360; + animation-timing-function: cubic-bezier(0.302435, 0.38135197, 0.55, 0.956352125); + } + 100% { + left: 343.6 * 100% / 360; + } +} + +@keyframes md-progress-linear-indeterminate-scale-2 { + 0% { + transform: scaleX(0.1); + animation-timing-function: cubic-bezier(0.205028172, 0.057050836, 0.57660995, 0.453970841); + } + 19.15% { + transform: scaleX(0.57); + animation-timing-function: cubic-bezier(0.152312994, 0.196431957, 0.648373778, 1.00431535); + } + 44.15% { + transform: scaleX(0.91); + animation-timing-function: cubic-bezier(0.25775882, -0.003163357, 0.211761916, 1.38178961); + } + 100% { + transform: scaleX(0.1); + } +} + +@keyframes md-progress-linear-indeterminate-2 { + 0% { + left: -197.6 * 100% / 360; + animation-timing-function: cubic-bezier(0.15, 0, 0.5150584, 0.409684966); + } + 25% { + left: -62.1 * 100% / 360; + animation-timing-function: cubic-bezier(0.3103299, 0.284057684, 0.8, 0.733718979); + } + 48.35% { + left: 106.2 * 100% / 360; + animation-timing-function: cubic-bezier(0.4, 0.627034903, 0.6, 0.902025796); + } + 100% { + left: 422.6 * 100% / 360; + } +} + + +md-progress-linear.md-#{$theme-name}-theme { + .md-container { + background-color: color($primary, '100'); + } + + .md-bar { + background-color: color($primary); + } + + &.md-warn .md-container { + background-color: color($warn, '100'); + } + + &.md-warn .md-bar { + background-color: color($warn); + } + + &.md-accent .md-container { + background-color: color($accent, '100'); + } + + &.md-accent .md-bar { + background-color: color($accent); + } + + &[md-mode=buffer] { + &.md-warn { + .md-bar1 { + background-color: color($warn, '100'); + } + .md-dashed:before { + background: radial-gradient(color($warn, '100') 0%, color($warn, '100') 16%, transparent 42%); + } + } + &.md-accent { + .md-bar1 { + background-color: color($accent, '100'); + } + .md-dashed:before { + background: radial-gradient(color($accent, '100') 0%, color($accent, '100') 16%, transparent 42%); + } + } + } +} diff --git a/app/templates/components/paper-progress-linear.hbs b/app/templates/components/paper-progress-linear.hbs new file mode 100644 index 000000000..a2e29982d --- /dev/null +++ b/app/templates/components/paper-progress-linear.hbs @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/tests/dummy/app/controllers/progress-linear.js b/tests/dummy/app/controllers/progress-linear.js new file mode 100644 index 000000000..2f96582fb --- /dev/null +++ b/tests/dummy/app/controllers/progress-linear.js @@ -0,0 +1,34 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + mode: 'query', + determinateValue: 30, + determinateValue2: 30, + + + init: function () { + this.setupTimer(); + this.setupTimer2(); + }, + + setupTimer: function() { + Ember.run.later(this, function() { + this.incrementProperty('determinateValue', 1); + this.incrementProperty('determinateValue2', 1.5); + if (this.get('determinateValue') > 100) { + this.set('determinateValue', 30); + this.set('determinateValue2', 30); + } + + Ember.run.later(this, this.setupTimer); + + }, 100); + }, + + setupTimer2: function() { + Ember.run.later(this, function() { + this.set('mode', this.get('mode') === 'query' ? 'determinate' : 'query'); + Ember.run.later(this, this.setupTimer2); + }, 7200); + } +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index 9b84d6c81..e10846444 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -10,6 +10,7 @@ Router.map(function() { this.route('button'); this.route('card'); this.route('checkbox'); + this.route('progress-linear'); this.route('radio'); this.route('switch'); this.route('typography'); diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index d81703cd2..1ae6586a0 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -18,6 +18,7 @@ {{#paper-item action="transitionTo" param="typography"}}Typography{{/paper-item}} {{#paper-item action="transitionTo" param="list"}}List{{/paper-item}} {{#paper-item action="transitionTo" param="list-controls"}}List Controls{{/paper-item}} + {{#paper-item action="transitionTo" param="progress-linear"}}Progress Linear{{/paper-item}} {{#paper-item action="transitionTo" param="divider"}}Divider{{/paper-item}} {{#paper-item action="transitionTo" param="card"}}Card{{/paper-item}} {{#paper-item action="transitionTo" param="button"}}Button{{/paper-item}} diff --git a/tests/dummy/app/templates/progress-linear.hbs b/tests/dummy/app/templates/progress-linear.hbs new file mode 100644 index 000000000..efc85997b --- /dev/null +++ b/tests/dummy/app/templates/progress-linear.hbs @@ -0,0 +1,35 @@ +{{#paper-toolbar}} +

+ {{#paper-sidenav-toggle class="menu-sidenav-toggle"}} + {{paper-icon icon="menu"}} + {{/paper-sidenav-toggle}} + Progress Linear +

+{{/paper-toolbar}} + +{{#paper-content classNames="md-padding"}} +
+

Basic Usage

+ +

Indeterminate mode

+ {{paper-progress-linear md-mode="indeterminate"}} + +

Buffer mode

+ {{paper-progress-linear classNames="md-warn" md-mode="buffer" value=determinateValue md-buffer-value=determinateValue2}} + +

Query mode

+ {{paper-progress-linear classNames="md-accent" md-mode=mode value=determinateValue}} + +

Template

+{{#code-block language='handlebars'}} +<h4>Indeterminate mode</h4> +\{{paper-progress-linear md-mode="indeterminate"}} + +<h4>Buffer mode</h4> +\{{paper-progress-linear classNames="md-warn" md-mode="buffer" value=determinateValue md-buffer-value=determinateValue2}} + +<h4>Query mode</h4> +\{{paper-progress-linear classNames="md-accent" md-mode=mode value=determinateValue}} +{{/code-block}} +
+{{/paper-content}} diff --git a/tests/unit/components/paper-progress-linear-test.js b/tests/unit/components/paper-progress-linear-test.js new file mode 100644 index 000000000..c077d1b29 --- /dev/null +++ b/tests/unit/components/paper-progress-linear-test.js @@ -0,0 +1,69 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-progress-linear', 'Unit | Component | paper progress linear', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'], + unit: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); + + + +test('it sets transform based on value', function(assert) { + + var component = this.subject({ + value: 50 + }); + + this.render(); + + var bar2 = this.$().find('.md-bar2')[0]; + + var bar2style = bar2.style['transform']; + + assert.equal(bar2style, 'translateX(-25%) scale(0.5, 1)', 'Transition set correctly'); + +}); + +test('it sets transform based on buffer value', function(assert) { + var component = this.subject({ + value: 50, + 'md-buffer-value': 75 + + }); + + this.render(); + + var bar1 = this.$().find('.md-bar1')[0]; + + var bar1style = bar1.style['transform']; + + assert.equal(bar1style, 'translateX(-12.5%) scale(0.75, 1)', 'Buffer bar transition set correctly'); +}); + +test('it should not set transition in query mode', function(assert) { + var component = this.subject({ + value: 80, + 'md-mode': 'query' + }); + + this.render(); + + var bar2 = this.$().find('.md-bar2')[0]; + + var bar2style = bar2.style['transform']; + + assert.ok(!bar2style, 'Buffer bar not set'); + +}); From de88c03cdbbf487fd4faf28b6cc6d4d15c8de9d0 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Wed, 15 Jul 2015 22:10:43 +0200 Subject: [PATCH 2/4] Add sniffer and browser-compatibility service to reflect ember-material-design and add correct css extension based on browsers. --- addon/components/paper-progress-linear.js | 7 +- app/services/browser-compatibility.js | 34 ++++++++++ app/services/sniffer.js | 68 +++++++++++++++++++ .../components/paper-progress-linear-test.js | 9 +-- 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 app/services/browser-compatibility.js create mode 100644 app/services/sniffer.js diff --git a/addon/components/paper-progress-linear.js b/addon/components/paper-progress-linear.js index 8904b0c4d..101a4d34a 100644 --- a/addon/components/paper-progress-linear.js +++ b/addon/components/paper-progress-linear.js @@ -13,6 +13,8 @@ export default Ember.Component.extend({ isInserted: false, + browserCompatibility: Ember.inject.service('browser-compatibility'), + init() { this._super(...arguments); this.setupTransforms(); @@ -25,7 +27,6 @@ export default Ember.Component.extend({ this.$('.md-container').addClass('md-ready'); }, - constants: Ember.inject.service('constants'), attributeBindings: ['md-mode', 'md-buffer-value'], @@ -38,7 +39,7 @@ export default Ember.Component.extend({ }, bar1Style: Ember.computed('clampedBufferValue', function() { - return new Ember.Handlebars.SafeString('transform: ' + this.transforms[this.get('clampedBufferValue')]); + return new Ember.Handlebars.SafeString(this.get('browserCompatibility.CSS.TRANSFORM') + ': ' + this.transforms[this.get('clampedBufferValue')]); }), bar2Style: Ember.computed('clampedValue', function() { @@ -47,7 +48,7 @@ export default Ember.Component.extend({ return new Ember.Handlebars.SafeString(''); } - return new Ember.Handlebars.SafeString('transform: ' + this.transforms[this.get('clampedValue')]); + return new Ember.Handlebars.SafeString(this.get('browserCompatibility.CSS.TRANSFORM') + ': ' + this.transforms[this.get('clampedValue')]); }), clampedValue: Ember.computed('value', function() { diff --git a/app/services/browser-compatibility.js b/app/services/browser-compatibility.js new file mode 100644 index 000000000..ce38d0ea0 --- /dev/null +++ b/app/services/browser-compatibility.js @@ -0,0 +1,34 @@ +import Ember from 'ember'; + +export default Ember.Service.extend({ + + snifferService: Ember.inject.service('sniffer'), + + webkit: Ember.computed('', function() { + return /webkit/i.test(this.get('snifferService.vendorPrefix')); + }), + + vendorProperty(name) { + var prefix = this.get('snifferService.vendorPrefix').toLowerCase(); + return this.get('webkit') ? `-webkit-${name.charAt(0)}${name.substring(1)}` : name; + }, + + CSS: Ember.computed('webkit', function() { + var webkit = this.get('webkit'); + return { + /* Constants */ + TRANSITIONEND: 'transitionend' + (webkit ? ' webkitTransitionEnd' : ''), + ANIMATIONEND: 'animationend' + (webkit ? ' webkitAnimationEnd' : ''), + + TRANSFORM: this.vendorProperty('transform'), + TRANSFORM_ORIGIN: this.vendorProperty('transformOrigin'), + TRANSITION: this.vendorProperty('transition'), + TRANSITION_DURATION: this.vendorProperty('transitionDuration'), + ANIMATION_PLAY_STATE: this.vendorProperty('animationPlayState'), + ANIMATION_DURATION: this.vendorProperty('animationDuration'), + ANIMATION_NAME: this.vendorProperty('animationName'), + ANIMATION_TIMING: this.vendorProperty('animationTimingFunction'), + ANIMATION_DIRECTION: this.vendorProperty('animationDirection') + }; + }) +}); diff --git a/app/services/sniffer.js b/app/services/sniffer.js new file mode 100644 index 000000000..a899047a6 --- /dev/null +++ b/app/services/sniffer.js @@ -0,0 +1,68 @@ +import Ember from 'ember'; + +const isString = value => { + return typeof value === 'string'; +}; + +var lowercase = (string) => { + return isString(string) ? string.toLowerCase() : string; +}; + +function toInt(str) { + return parseInt(str, 10); +} + + +export default Ember.Service.extend({ + vendorPrefix: '', + transitions: false, + animations: false, + document: document, + window: window, + + android: Ember.computed('', function() { + return toInt((/android (\d+)/.exec(lowercase((this.get('window').navigator || {}).userAgent)) || [])[1]); + }), + + init() { + this._super(...arguments); + + var bodyStyle = this.get('document').body && this.get('document').body.style; + var vendorPrefix; + var vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/; + + var transitions = false; + var animations = false; + var match; + + if (bodyStyle) { + for (var prop in bodyStyle) { + if (match = vendorRegex.exec(prop)) { + vendorPrefix = match[0]; + vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); + break; + } + } + + if (!vendorPrefix) { + vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; + } + + transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); + animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); + + if (this.get('android') && (!transitions || !animations)) { + transitions = isString(bodyStyle.webkitTransition); + animations = isString(bodyStyle.webkitAnimation); + } + + + } + + this.set('transitions', transitions); + this.set('animations', animations); + + this.set('vendorPrefix', vendorPrefix); + } + +}); diff --git a/tests/unit/components/paper-progress-linear-test.js b/tests/unit/components/paper-progress-linear-test.js index c077d1b29..fb0c2b2d7 100644 --- a/tests/unit/components/paper-progress-linear-test.js +++ b/tests/unit/components/paper-progress-linear-test.js @@ -3,7 +3,8 @@ import { moduleForComponent, test } from 'ember-qunit'; moduleForComponent('paper-progress-linear', 'Unit | Component | paper progress linear', { // Specify the other units that are required for this test // needs: ['component:foo', 'helper:bar'], - unit: true + unit: true, + needs: ['service:browser-compatibility', 'service:sniffer'] }); test('it renders', function(assert) { @@ -30,7 +31,7 @@ test('it sets transform based on value', function(assert) { var bar2 = this.$().find('.md-bar2')[0]; - var bar2style = bar2.style['transform']; + var bar2style = bar2.style[component.get('browserCompatibility.CSS.TRANSFORM')]; assert.equal(bar2style, 'translateX(-25%) scale(0.5, 1)', 'Transition set correctly'); @@ -47,7 +48,7 @@ test('it sets transform based on buffer value', function(assert) { var bar1 = this.$().find('.md-bar1')[0]; - var bar1style = bar1.style['transform']; + var bar1style = bar1.style[component.get('browserCompatibility.CSS.TRANSFORM')]; assert.equal(bar1style, 'translateX(-12.5%) scale(0.75, 1)', 'Buffer bar transition set correctly'); }); @@ -62,7 +63,7 @@ test('it should not set transition in query mode', function(assert) { var bar2 = this.$().find('.md-bar2')[0]; - var bar2style = bar2.style['transform']; + var bar2style = bar2.style[component.get('browserCompatibility.CSS.TRANSFORM')]; assert.ok(!bar2style, 'Buffer bar not set'); From b26e3f7601a586d378b6391d2de107dad9417691 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Sun, 19 Jul 2015 14:48:26 +0200 Subject: [PATCH 3/4] Also add paper-progress-circular since - its trivial to do in the same pull request --- addon/components/paper-progress-circular.js | 30 +++ app/components/paper-progress-circular.js | 1 + app/styles/ember-paper.scss | 1 + app/styles/paper-progress-circular.scss | 249 ++++++++++++++++++ .../components/paper-progress-circular.hbs | 11 + .../app/controllers/progress-circular.js | 22 ++ tests/dummy/app/router.js | 1 + tests/dummy/app/templates/application.hbs | 1 + .../dummy/app/templates/progress-circular.hbs | 48 ++++ 9 files changed, 364 insertions(+) create mode 100644 addon/components/paper-progress-circular.js create mode 100644 app/components/paper-progress-circular.js create mode 100644 app/styles/paper-progress-circular.scss create mode 100644 app/templates/components/paper-progress-circular.hbs create mode 100644 tests/dummy/app/controllers/progress-circular.js create mode 100644 tests/dummy/app/templates/progress-circular.hbs diff --git a/addon/components/paper-progress-circular.js b/addon/components/paper-progress-circular.js new file mode 100644 index 000000000..e8e83aab5 --- /dev/null +++ b/addon/components/paper-progress-circular.js @@ -0,0 +1,30 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + browserCompatibility: Ember.inject.service('browser-compatibility'), + + classNames: ['md-default-theme'], + + tagName: 'md-progress-circular', + + attributeBindings: ['value', 'md-mode'], + + mdDiameter: 48, + + scale: Ember.computed('mdDiameter', function() { + return this.get('mdDiameter') / 48; + }), + + clampedValue: Ember.computed('value', function() { + + var value = this.get('value'); + + return Math.max(0, Math.min(value || 0, 100)); + + }), + + circleStyle: Ember.computed('scale', function() { + return Ember.String.htmlSafe(this.get('browserCompatibility.CSS.TRANSFORM') + ': ' + 'scale(' + this.get('scale').toString() + ')'); + }) + +}); diff --git a/app/components/paper-progress-circular.js b/app/components/paper-progress-circular.js new file mode 100644 index 000000000..b236382e4 --- /dev/null +++ b/app/components/paper-progress-circular.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-progress-circular'; diff --git a/app/styles/ember-paper.scss b/app/styles/ember-paper.scss index 59cd44db8..6ee9f607c 100644 --- a/app/styles/ember-paper.scss +++ b/app/styles/ember-paper.scss @@ -76,6 +76,7 @@ @import 'paper-slider'; @import 'paper-subheader'; @import 'paper-progress-linear'; +@import 'paper-progress-circular'; @import 'paper-sidenav'; @import 'paper-backdrop'; diff --git a/app/styles/paper-progress-circular.scss b/app/styles/paper-progress-circular.scss new file mode 100644 index 000000000..68ea8a59b --- /dev/null +++ b/app/styles/paper-progress-circular.scss @@ -0,0 +1,249 @@ +$progress-circular-ease-in-out : cubic-bezier(0.35, 0, 0.25, 1) !default; +$progress-circular-duration : 5.25s !default; +$progress-circular-circle-duration : $progress-circular-duration * 0.25 !default; +$progress-circular-outer-duration : $progress-circular-duration * (5 / 9) !default; +$progress-circular-sporadic-duration : $progress-circular-duration !default; +$progress-circular-size : 50px !default; + +@keyframes outer-rotate { + 100% { transform: rotate(360deg); } +} +@keyframes left-wobble { + 0%, 100% { transform: rotate(130deg); } + 50% { transform: rotate( -5deg); } +} +@keyframes right-wobble { + 0%, 100% { transform: rotate(-130deg); } + 50% { transform: rotate( 5deg); } +} +@keyframes sporadic-rotate { + 12.5% { transform: rotate( 135deg); } + 25% { transform: rotate( 270deg); } + 37.5% { transform: rotate( 405deg); } + 50% { transform: rotate( 540deg); } + 62.5% { transform: rotate( 675deg); } + 75% { transform: rotate( 810deg); } + 87.5% { transform: rotate( 945deg); } + 100% { transform: rotate(1080deg); } +} + +md-progress-circular { + width: $progress-circular-size; + height: $progress-circular-size; + display: block; + position: relative; + padding-top: 0 !important; + margin-bottom: 0 !important; + overflow: hidden; + .md-inner { + width: $progress-circular-size; + height: $progress-circular-size; + position: relative; + .md-gap { + position: absolute; + left: $progress-circular-size * 0.5 - 1; + right: $progress-circular-size * 0.5 - 1; + top: 0; + bottom: 0; + border-top-width: 5px; + border-top-style: solid; + box-sizing: border-box; + } + .md-left, .md-right { + position: absolute; + top: 0; + height: $progress-circular-size; + width: $progress-circular-size * 0.5; + overflow: hidden; + .md-half-circle { + position: absolute; + top: 0; + width: $progress-circular-size; + height: $progress-circular-size; + box-sizing: border-box; + border-width: 5px; + border-style: solid; + border-bottom-color: transparent; + border-radius: 50%; + } + } + .md-left { + left: 0; + .md-half-circle { + left: 0; + border-right-color: transparent; + } + } + .md-right { + right: 0; + .md-half-circle { + right: 0; + border-left-color: transparent; + } + } + } + + // TODO: This while-loop generates about 2 kilobytes of css after gzip. + // Refactor progressCircular to animate with javascript. + $i: 0; + @while $i <= 100 { + &[value="#{$i}"] { + .md-inner { + .md-left { + .md-half-circle { + @if $i <= 50 { + transform: rotate(135deg); + } @else { + transition: transform 0.1s linear; + $deg: ($i - 50) / 50 * 180 + 135; + transform: rotate(#{$deg}deg); + } + } + } + .md-right { + .md-half-circle { + @if $i <= 50 { + transition: transform 0.1s linear; + $deg: $i / 50 * 180 - 135; + transform: rotate(#{$deg}deg); + } @else { + transform: rotate(45deg); + } + } + } + .md-gap { + border-bottom-width: 5px; + border-bottom-style: solid; + @if $i <= 50 { + border-bottom-color: transparent !important; + } @else { + transition: border-bottom-color 0.1s linear; + } + } + } + } + $i: $i + 1; + } + + &[md-mode=indeterminate] { + .md-spinner-wrapper { + animation: outer-rotate $progress-circular-outer-duration linear infinite; + .md-inner { + animation: sporadic-rotate $progress-circular-sporadic-duration $progress-circular-ease-in-out infinite; + .md-left, .md-right { + .md-half-circle { + animation-iteration-count: infinite; + animation-duration: ($progress-circular-duration * 0.25); + animation-timing-function: $progress-circular-ease-in-out; + } + } + .md-left { + .md-half-circle { + animation-name: left-wobble; + } + } + .md-right { + .md-half-circle { + animation-name: right-wobble; + } + } + } + } + } +} + +.ng-hide md-progress-circular, +md-progress-circular.ng-hide { + &[md-mode=indeterminate] { + .md-spinner-wrapper { + animation: none; + .md-inner { + animation: none; + .md-left { + .md-half-circle { + animation-name: none; + } + } + .md-right { + .md-half-circle { + animation-name: none; + } + } + } + } + } +} + +// THEME +md-progress-circular.md-#{$theme-name}-theme { + background-color: transparent; + .md-inner { + .md-gap { + border-top-color: color($primary); + border-bottom-color: color($primary); + } + .md-left, .md-right { + .md-half-circle { + border-top-color: color($primary); + } + } + .md-right { + .md-half-circle { + border-right-color: color($primary); + } + } + .md-left { + .md-half-circle { + border-left-color: color($primary); + } + } + } + + &.md-warn { + .md-inner { + .md-gap { + border-top-color: color($warn); + border-bottom-color: color($warn); + } + .md-left, .md-right { + .md-half-circle { + border-top-color: color($warn); + } + } + .md-right { + .md-half-circle { + border-right-color: color($warn); + } + } + .md-left { + .md-half-circle { + border-left-color: color($warn); + } + } + } + } + + &.md-accent { + .md-inner { + .md-gap { + border-top-color: color($accent); + border-bottom-color: color($accent); + } + .md-left, .md-right { + .md-half-circle { + border-top-color: color($accent); + } + } + .md-right { + .md-half-circle { + border-right-color: color($accent); + } + } + .md-left { + .md-half-circle { + border-left-color: color($accent); + } + } + } + } +} diff --git a/app/templates/components/paper-progress-circular.hbs b/app/templates/components/paper-progress-circular.hbs new file mode 100644 index 000000000..cdf1af793 --- /dev/null +++ b/app/templates/components/paper-progress-circular.hbs @@ -0,0 +1,11 @@ +
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/dummy/app/controllers/progress-circular.js b/tests/dummy/app/controllers/progress-circular.js new file mode 100644 index 000000000..e1ef55629 --- /dev/null +++ b/tests/dummy/app/controllers/progress-circular.js @@ -0,0 +1,22 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + mode: 'query', + determinateValue: 30, + + init: function () { + this.setupTimer(); + }, + + setupTimer: function() { + Ember.run.later(this, function() { + this.incrementProperty('determinateValue', 1); + if (this.get('determinateValue') > 100) { + this.set('determinateValue', 30); + } + + Ember.run.later(this, this.setupTimer); + + }, 100); + } +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index e10846444..0e0ad962c 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -10,6 +10,7 @@ Router.map(function() { this.route('button'); this.route('card'); this.route('checkbox'); + this.route('progress-circular'); this.route('progress-linear'); this.route('radio'); this.route('switch'); diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 1ae6586a0..3f217ecc8 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -18,6 +18,7 @@ {{#paper-item action="transitionTo" param="typography"}}Typography{{/paper-item}} {{#paper-item action="transitionTo" param="list"}}List{{/paper-item}} {{#paper-item action="transitionTo" param="list-controls"}}List Controls{{/paper-item}} + {{#paper-item action="transitionTo" param="progress-circular"}}Progress Circular{{/paper-item}} {{#paper-item action="transitionTo" param="progress-linear"}}Progress Linear{{/paper-item}} {{#paper-item action="transitionTo" param="divider"}}Divider{{/paper-item}} {{#paper-item action="transitionTo" param="card"}}Card{{/paper-item}} diff --git a/tests/dummy/app/templates/progress-circular.hbs b/tests/dummy/app/templates/progress-circular.hbs new file mode 100644 index 000000000..9d3d5ab80 --- /dev/null +++ b/tests/dummy/app/templates/progress-circular.hbs @@ -0,0 +1,48 @@ +{{#paper-toolbar}} +

+ {{#paper-sidenav-toggle class="menu-sidenav-toggle"}} + {{paper-icon icon="menu"}} + {{/paper-sidenav-toggle}} + Progress Circular +

+{{/paper-toolbar}} + +{{#paper-content classNames="md-padding"}} +
+

Basic Usage

+

Determinate

+

For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take.

+
+ {{paper-progress-circular md-mode="determinate" value=determinateValue}} +
+ +

Indeterminate

+

For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.

+
+ {{paper-progress-circular md-mode="indeterminate"}} +
+ +

Theming

+ +
+ {{paper-progress-circular class="md-hue-2" md-mode="indeterminate"}} + {{paper-progress-circular class="md-accent" md-mode="indeterminate"}} + {{paper-progress-circular class="md-accent md-hue-1" md-mode="indeterminate"}} + {{paper-progress-circular class="md-warn md-hue-3" md-mode="indeterminate"}} + {{paper-progress-circular class="md-warn" md-mode="indeterminate"}} +
+ + +

Template

+{{#code-block language='handlebars'}} +\{{paper-progress-circular md-mode="determinate" value=determinateValue}} +\{{paper-progress-circular md-mode="indeterminate"}} + +\{{paper-progress-circular class="md-hue-2" md-mode="indeterminate"}} +\{{paper-progress-circular class="md-accent" md-mode="indeterminate"}} +\{{paper-progress-circular class="md-accent md-hue-1" md-mode="indeterminate"}} +\{{paper-progress-circular class="md-warn md-hue-3" md-mode="indeterminate"}} +\{{paper-progress-circular class="md-warn" md-mode="indeterminate"}} +{{/code-block}} +
+{{/paper-content}} From 06e3ab9c95b0308928ec6aea5aacaa865efa3765 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Sun, 19 Jul 2015 15:13:54 +0200 Subject: [PATCH 4/4] Add tests for paper-progress-circular --- addon/components/paper-progress-circular.js | 4 +- .../paper-progress-circular-test.js | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/unit/components/paper-progress-circular-test.js diff --git a/addon/components/paper-progress-circular.js b/addon/components/paper-progress-circular.js index e8e83aab5..68de66a25 100644 --- a/addon/components/paper-progress-circular.js +++ b/addon/components/paper-progress-circular.js @@ -1,5 +1,7 @@ import Ember from 'ember'; +var BASE_DIAMETER = 48; + export default Ember.Component.extend({ browserCompatibility: Ember.inject.service('browser-compatibility'), @@ -12,7 +14,7 @@ export default Ember.Component.extend({ mdDiameter: 48, scale: Ember.computed('mdDiameter', function() { - return this.get('mdDiameter') / 48; + return this.get('mdDiameter') / BASE_DIAMETER; }), clampedValue: Ember.computed('value', function() { diff --git a/tests/unit/components/paper-progress-circular-test.js b/tests/unit/components/paper-progress-circular-test.js new file mode 100644 index 000000000..dc87da5a2 --- /dev/null +++ b/tests/unit/components/paper-progress-circular-test.js @@ -0,0 +1,55 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-progress-circular', 'Unit | Component | paper progress circular', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'], + unit: true, + needs: ['service:browser-compatibility', 'service:sniffer'] +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); + + + +test('it sets transform scale 1 by default', function(assert) { + + var component = this.subject({ + value: 50 + }); + + this.render(); + + var spinnerWrapper = this.$().find('.md-spinner-wrapper')[0]; + + var spinnerWrapperStyle = spinnerWrapper.style[component.get('browserCompatibility.CSS.TRANSFORM')]; + + assert.equal(spinnerWrapperStyle, 'scale(1)', 'Transform set correctly'); + +}); + +test('it sets transform scale 2 when diameter is 96', function(assert) { + + var component = this.subject({ + value: 50, + mdDiameter: 96 + }); + + this.render(); + + var spinnerWrapper = this.$().find('.md-spinner-wrapper')[0]; + + var spinnerWrapperStyle = spinnerWrapper.style[component.get('browserCompatibility.CSS.TRANSFORM')]; + + assert.equal(spinnerWrapperStyle, 'scale(2)', 'Transform set correctly'); + +});