Skip to content

Instantly share code, notes, and snippets.

@foxnewsnetwork
Last active June 6, 2017 18:03
Show Gist options
  • Save foxnewsnetwork/1848180b728ee69e447d714405f12d57 to your computer and use it in GitHub Desktop.
Save foxnewsnetwork/1848180b728ee69e447d714405f12d57 to your computer and use it in GitHub Desktop.
New Twiddle
import Ember from 'ember';
const { RSVP } = Ember;
const { anime } = window;
const scrollHolder = async (i) => new RSVP.Promise((resolve) => anime({
targets: '.card-scroller',
translateX: -i * 225,
easing: 'easeInOutSine',
duration: 750,
complete: resolve
}))
const spinCard = async (i) => new RSVP.Promise((resolve) => anime({
targets: `.card__no-${i}`,
rotate: 360,
easing: 'easeInOutSine',
duration: 750,
complete: resolve
}))
const unspinCard = async (i) => new RSVP.Promise((resolve) => anime({
targets: `.card__no-${i}`,
rotate: 0,
easing: 'easeInOutSine',
duration: 750,
complete: resolve
}))
export default function(nextCardIndex, prevCardIndex) {
return RSVP.all([
scrollHolder(nextCardIndex),
spinCard(nextCardIndex),
unspinCard(prevCardIndex)
])
}
import Ember from 'ember';
import { task } from 'ember-concurrency';
import layout from '../templates/components/anime-bind';
const {
computed,
getProperties,
RSVP,
get,
isPresent
} = Ember;
const equality = (a, b) => isPresent(a) && isPresent(b) && a === b;
const DEFAULT_ANIME_FN = RSVP.resolve;
const AnimeBind = Ember.Component.extend({
layout,
tagName: '',
equality,
animeFns: [DEFAULT_ANIME_FN],
animeTask: task(function*(nextVal, prevVal) {
const { animeFns } = getProperties(this, 'animeFns');
this.sendAction('start', nextVal, prevVal);
for (let i = 0; i < get(animeFns, 'length'); i++) {
const animeFn = animeFns[i];
yield animeFn(nextVal, prevVal);
}
this.notifyPropertyChange('animeValue');
this.sendAction('finish', nextVal, prevVal);
}).restartable(),
value: computed({
set(key, nextVal, prevVal) {
const { equality, animeTask } = getProperties(this, 'equality', 'animeTask');
if (!equality(nextVal, prevVal)) {
animeTask.perform(nextVal, prevVal);
}
return nextVal;
}
}),
animeValue: computed({
get() {
return this.get('value');
}
}).readOnly()
});
AnimeBind.reopenClass({
positionalParams: ['value']
});
export default AnimeBind;
import Ember from 'ember';
const {
setProperties,
get,
tryInvoke
} = Ember;
const DEFAULT_PARENT_API = {
registerParent(comp) {
Ember.Logger.error(
'[anime/anime-stacks-child-card]',
'uh-oh, it looks like you tried to use the',
'{{anime-stacks-child-card}} component on its own',
'instead of consuming it as it\'s passed down to you from',
'the {{anime-bind}} component. You are doing it wrong!',
comp
);
throw 'component misuse error';
}
};
const ASCC = Ember.Component.extend({
parentAPI: DEFAULT_PARENT_API,
tagName: '',
/**
* Late-bound attributes.
*
* These attributes are given from the parent anime-stacks
* component after this component has been inserted
*/
cardIndex: null,
unregisterChild: () => {},
/**
* end of late-bound attributes
*/
didInsertElement() {
const { registerParent } = get(this, 'parentAPI');
const { cardIndex, unregister } = registerParent(this);
setProperties(this, { cardIndex, unregister });
},
willDestroyElement() {
tryInvoke(this, 'unregister');
}
});
ASCC.reopenClass({
positionalParams: ['parentAPI']
});
export default ASCC;
import Ember from 'ember';
import layout from '../templates/components/anime-stacks';
import { Pair } from '../utils/ds';
import { call } from '../utils/computed';
const { RSVP, get, computed, set } = Ember;
export default Ember.Component.extend({
layout,
tagName: '',
childrenCardComponents: computed({
get() { return Ember.A([]); }
}).readOnly(),
indexPairs: computed({
get() {
const pair = Pair.create();
pair.push(0);
return pair;
}
}).readOnly(),
activeIndex: computed('indexPairs.last', {
get() {
return get(this, 'indexPairs.last');
},
set(key, val) {
const pairs = get(this, 'indexPairs');
pairs.push(val);
return pairs.get('last');
}
}),
animeFns: [RSVP.resolve],
isForward: call('indexPairs.first', 'indexPairs.last', (a, b) => a < b),
registerChild: computed({
get() {
const ccs = get(this, 'childrenCardComponents');
const register = (childCardComp) => {
const cardIndex = get(ccs, 'length');
ccs.pushObject(childCardComp);
const unregister = () => {
ccs.removeObject(childCardComp);
css.map((card) => register(card));
};
return { cardIndex, unregister };
}
return register;
}
}).readOnly(),
actions: {
start(i) {
this.sendAction('start', i);
},
finish(i) {
this.sendAction('finish', i);
}
}
});
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle'
});
import Ember from 'ember';
import animeFn from '../animations/index';
export default Ember.Controller.extend({
activeIndex: 0,
animeFns: [animeFn]
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
* {
box-sizing: border-box;
}
.card {
width: 225px;
height: 225px;
border: 1px solid #666;
align-items: center;
justify-content: center;
background-color: #ccc;
display: inline-flex;
}
.card--inactive {
background-color: #777;
}
.cards {
height: 300px;
width: 100%;
background-color: #eee;
overflow: hidden;
display: flex;
align-items: center;
}
.card-scroller {
white-space: nowrap;
}
<h1>Welcome to {{appName}}</h1>
<br>
<br>
{{outlet}}
<br>
<br>
{{yield animeValue animeTask.isRunning}}
{{yield (eq cardIndex activeIndex) cardIndex}}
{{#anime-bind activeIndex
animeFns=(if isForward animeFns (reverse animeFns))
start='start'
finish='finish' as |animeIndex|}}
{{yield (hash
card=(component
'anime-stacks-child-card'
(hash registerParent=registerChild)
activeIndex=animeIndex))}}
{{/anime-bind}}
<div class='cards'>
<div class='card-scroller'>
{{#anime-stacks
animeFns=animeFns
activeIndex=activeIndex as |stacks|}}
{{#stacks.card as |isActive index|}}
<div class='card {{if isActive 'card--is-active' 'card--inactive'}} card__no-{{index}}'>
card no. {{index}}
</div>
{{/stacks.card}}
{{#stacks.card as |isActive index|}}
<div class='card {{if isActive 'card--is-active' 'card--inactive'}} card__no-{{index}}'>
card no. {{index}}
</div>
{{/stacks.card}}
{{#stacks.card as |isActive index|}}
<div class='card {{if isActive 'card--is-active' 'card--inactive'}} card__no-{{index}}'>
card no. {{index}}
</div>
{{/stacks.card}}
{{#stacks.card as |isActive index|}}
<div class='card {{if isActive 'card--is-active' 'card--inactive'}} card__no-{{index}}'>
card no. {{index}}
</div>
{{/stacks.card}}
{{/anime-stacks}}
</div>
</div>
<div class='debug-stuff'>
activeIndex: {{activeIndex}}
</div>
{{#if (gt activeIndex 0)}}
<button {{action (mut activeIndex) (dec activeIndex)}}>
Decrement 1
</button>
{{/if}}
{{#if (lt activeIndex 4)}}
<button {{action (mut activeIndex) (inc activeIndex)}}>
Increment 1
</button>
{{/if}}
{
"version": "0.12.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"animejs": "https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.js",
"ember": "2.12.0",
"ember-template-compiler": "2.12.0",
"ember-testing": "2.12.0"
},
"addons": {
"ember-data": "2.12.1",
"ember-concurrency": "0.8.5",
"ember-composable-helpers": "2.0.1",
"ember-truth-helpers": "1.3.0"
}
}
import Ember from 'ember';
const { computed, get } = Ember;
export function call(...args) {
const [...keys] = args.slice(0, -1);
const [fn] = args.slice(-1);
return computed(...keys, {
get() {
const values = keys.map(key => get(this, key));
return fn(...values, this);
}
}).readOnly();
}
import Ember from 'ember';
import { call } from './computed';
const {
computed,
computed: {
readOnly
}
} = Ember;
export const Pair = Ember.Object.extend({
pair: computed(() => Ember.A([])),
push(x) {
const pair = this.get('pair');
pair.pushObject(x);
if (pair.get('length') > 2) {
pair.shiftObject();
}
},
last: readOnly('pair.lastObject'),
first: readOnly('pair.firstObject'),
isMismatch: call('first', 'last', (x, y) => x !== y),
string: call('first', 'last', (f, l) => `[${f}, ${l}]`)
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment