Display a list of streamers and their status using the twitch.tv API. The user may add/remove/filter streamers from the list.
A Pen by Matthew Kuo on CodePen.
<html ng-app="stream"> | |
<body ng-controller="GlobalController as globalCtrl"> | |
<div data-alert class="alert-box alert" ng-show="globalCtrl.alertMsg"> | |
{{ globalCtrl.alertMsg }} | |
<a href ng-click="globalCtrl.setAlert('')" class="close">×</a> | |
</div> | |
<div id="wrap" ng-controller="TabsController as tabs"> | |
<section id="tabs"> | |
<dl class="tabs"> | |
<dd ng-class="{active:tabs.isSelected(1)}"><a href ng-click="tabs.selectTab(1)"><i class="fa fa-users"></i></a></dd> | |
<dd ng-class="{active:tabs.isSelected(2)}"><a href ng-click="tabs.selectTab(2)"><i class="fa fa-user"></i></a></dd> | |
<dd ng-class="{active:tabs.isSelected(3)}"><a href ng-click="tabs.selectTab(3)"><i class="fa fa-user-times"></i></a></dd> | |
</dl> | |
</section> | |
<section id="streamList" ng-controller="StreamListController as streamList"> | |
<div class="search clearfix" ng-controller="AddStreamController as addStreamCtrl"> | |
<i class="fa fa-search"></i> | |
<input type="text" placeholder="Search..." ng-model="streamList.searchText" ng-focus="addStreamCtrl.toggleAnimation(false)" ng-blur="addStreamCtrl.toggleAnimation(true)"> | |
<a href class="add-streamer" ng-class="{activated:addStreamCtrl.isAddClicked}" ng-click="addStreamCtrl.onAddStreamClicked()"> | |
<i class="fa fa-user-plus"></i> | |
</a> | |
<div class="bubble clearfix" ng-show="addStreamCtrl.isAddClicked"> | |
<form ng-submit="addStreamCtrl.submitNewStreamer()"> | |
<input type="text" placeholder="Add streamer..." ng-model="addStreamCtrl.newStreamer"/> | |
<input type="submit" class="fa fa-plus" value=""/> | |
</form> | |
</div> | |
</div> | |
<ul> | |
<li ng-show="tabs.isSelected(1) || tabs.isSelected(2) && streamer.online || tabs.isSelected(3) && !streamer.online" | |
ng-repeat="streamer in streamList.streamers | filter:streamList.searchText | orderBy:['-online', 'display_name']" | |
ng-controller="StreamerController as streamerCtrl" | |
ng-mouseover="streamerCtrl.hover(true)" ng-mouseleave="streamerCtrl.hover(false)"> | |
<a href="{{ streamer.url }}" target="_blank"> | |
<div class="logo"> | |
<img ng-src="{{streamer.logo}}"/> | |
<div class="status-icon" ng-class="{online:streamer.online}"></div> | |
</div> | |
<div class="info"> | |
<div class="display-name">{{ streamer.display_name }}</div> | |
<div ng-show="streamer.online" class="status">{{ streamer.status }}</div> | |
</div> | |
</a> | |
<div class="delete-streamer" ng-show="streamerCtrl.getHoveredState()" ng-click="streamList.removeStreamer(streamer)"> | |
<i class="fa fa-times"></i> | |
</div> | |
</li> | |
</ul> | |
</section> | |
</div> | |
</body> | |
</html> |
(function() { | |
var app = angular.module('stream', ['ngAnimate']); | |
app.controller('GlobalController', ['$scope', '$timeout', function($scope, $timeout) { | |
myThis = this; | |
this.alertMsg = ''; | |
this.setAlert = function(text) { | |
this.alertMsg = text; | |
}; | |
// Emitted by StreamListController | |
$scope.$on('getUserFailed', function(event, msg) { | |
myThis.alertMsg = msg; | |
$timeout(function() { | |
myThis.alertMsg = ''; | |
}, 8000); | |
}); | |
}]); | |
app.controller('TabsController', function() { | |
this.tab = 1; | |
this.selectTab = function(selectedTab) { | |
this.tab = selectedTab; | |
}; | |
this.isSelected = function(selectedTab) { | |
return this.tab === selectedTab; | |
}; | |
}); | |
app.controller('AddStreamController', ['$animate', '$scope', function($animate, $scope) { | |
this.isAddClicked = false; | |
this.newStreamer = ''; | |
this.onAddStreamClicked = function() { | |
$animate.enabled(true); | |
this.isAddClicked = !this.isAddClicked; | |
}; | |
this.submitNewStreamer = function() { | |
$scope.$emit('streamerSubmitted', this.newStreamer); | |
this.newStreamer = ''; | |
this.isAddClicked = false; | |
}; | |
this.toggleAnimation = function(isEnabled) { | |
$animate.enabled(isEnabled); | |
}; | |
}]); | |
app.controller('StreamListController', ['$http', '$scope', '$animate', function($http, $scope, $animate) { | |
var myThis = this; | |
this.streamers = []; | |
var getStreamData = function(streamer) { | |
var streamUrl = 'https://api.twitch.tv/kraken/streams/' + streamer + '?callback=JSON_CALLBACK'; | |
$http.jsonp(streamUrl).success(function(data) { | |
if (data && data.stream) { | |
var channel = data.stream.channel; | |
myThis.streamers.push({ | |
'online': true, | |
'display_name': channel.display_name, | |
'status': channel.status, | |
'game': channel.game, | |
'url': 'http://www.twitch.tv/' + streamer, | |
'logo': channel.logo || 'http://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png' | |
}); | |
} else if (data.error) { | |
$scope.$emit('getUserFailed', data.message); | |
} else { | |
getUserData(streamer); | |
} | |
}); | |
}; | |
var getUserData = function(streamer) { | |
var userUrl = 'https://api.twitch.tv/kraken/users/' + streamer + '?callback=JSON_CALLBACK'; | |
$http.jsonp(userUrl).success(function(data) { | |
myThis.streamers.push({ | |
'online': false, | |
'display_name': data.display_name, | |
'url': 'http://www.twitch.tv/' + streamer, | |
'logo': data.logo || 'http://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png' | |
}); | |
}).error(function(data) { | |
$scope.$emit('getUserFailed', data.message); | |
}); | |
}; | |
this.addStream = function(name) { | |
getStreamData(name); | |
}; | |
defaultStreamers.forEach(function(streamer) { | |
getStreamData(streamer); | |
}); | |
$scope.$on('streamerSubmitted', function(event, streamer) { | |
myThis.addStream(streamer); | |
}); | |
this.removeStreamer = function(streamer) { | |
for (var i = 0; i < this.streamers.length; i++) { | |
if (this.streamers[i].$$hashKey === streamer.$$hashKey) { | |
this.streamers.splice(i, 1); | |
} | |
} | |
}; | |
}]); | |
app.controller('StreamerController', function($scope) { | |
this.hovered = false; | |
this.hover = function(isHovered) { | |
this.hovered = isHovered; | |
}; | |
this.getHoveredState = function() { | |
return this.hovered; | |
}; | |
}); | |
var defaultStreamers = ["Doublelift", "Phantoml0rd", "TheOddOne", "medrybw", "Bjergson"]; | |
})(); |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular-animate.min.js"></script> |
@import 'compass'; | |
@import url(http://fonts.googleapis.com/css?family=Raleway); | |
$light-grey: #ecf0f1; | |
$flat-red: #c0392b; | |
$flat-green: #27ae60; | |
$flat-blue: #4aa3df; | |
$darker-flat-blue: #2980b9; | |
body { | |
background: url('http://i1167.photobucket.com/albums/q633/mattkuo/website-backgrounds/escheresque_ste_zps4eq0cibc.png'); | |
font-family: Raleway, Helvetica, Roboto, Arial, sans-serif; | |
} | |
.alert-box.alert { | |
position: absolute; | |
top: 0; | |
text-align: center; | |
width: 100%; | |
padding: 0.5rem; | |
&.ng-hide-add { | |
@include animation(slideOutUp 1s); | |
} | |
&.ng-hide-remove { | |
@include animation(slideInDown 1s); | |
} | |
} | |
#wrap { | |
width: 300px; | |
margin: 0 auto 40px; | |
position: relative; | |
top: 40px; | |
background: transparent; | |
@include box-shadow(); | |
} | |
#streamList { | |
& .search { | |
position: relative; | |
& > i.fa-search { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
border-left: none; | |
border-right: none; | |
z-index: 1; | |
} | |
& > input { | |
float: left; | |
margin: 0; | |
padding-left: 2rem; | |
width: 80%; | |
} | |
& a.add-streamer { | |
float: left; | |
width: 20%; | |
text-align: center; | |
background: $flat-blue; | |
color: white; | |
& > i { | |
line-height: 2.3125rem; | |
} | |
&.activated { | |
background-color: #2980b9; | |
} | |
} | |
& .bubble { | |
opacity: 0.9; | |
position: absolute; | |
right: 3px; | |
top: 30px; | |
background-color: $light-grey; | |
padding: 15px; | |
margin: 1rem 0 3rem; | |
z-index: 100; | |
@include border-radius(4px); | |
@include transition(opacity ease-in-out 0.2s); | |
&.ng-hide-add { | |
@include animation(fadeOutUp 0.5s); | |
} | |
&.ng-hide-remove { | |
@include animation(flipInX 0.8s); | |
} | |
& input[type="text"] { | |
float: left; | |
width: 80%; | |
} | |
& input[type="submit"] { | |
float: left; | |
width: 20%; | |
line-height: 2.3125rem; | |
color: rgba(0, 0, 0, 0.75); | |
cursor: pointer; | |
} | |
&:after { | |
content: ''; | |
position: absolute; | |
width: 0; | |
border-width: 0 15px 15px; | |
border-style: solid; | |
border-color: $light-grey transparent; | |
top: -15px; | |
right: 15px; | |
} | |
& input { | |
margin: 0; | |
padding: 0; | |
background: transparent; | |
border: 0; | |
box-shadow: none; | |
} | |
} | |
} | |
ul { | |
margin: 0; | |
& li { | |
position: relative; | |
list-style: none; | |
background: #fff; | |
padding: 16px; | |
border-bottom: 1px solid $light-grey; | |
& .delete-streamer { | |
position: absolute; | |
width: 20%; | |
height: 83px; | |
background: $flat-red; | |
top: 0; | |
right: 0; | |
text-align: center; | |
cursor: pointer; | |
& i { | |
line-height: 83px; | |
color: white; | |
} | |
&.ng-hide-add { | |
@include animation(fadeOut 0.3s); | |
} | |
&.ng-hide-remove { | |
@include animation(fadeIn 0.3s); | |
} | |
} | |
&.ng-enter { | |
@include animation(zoomInLeft 0.5s); | |
} | |
&.ng-leave { | |
@include animation(zoomOutRight 0.5s); | |
} | |
& a { | |
display: table; | |
color: inherit; | |
& > div { | |
display: table-cell; | |
vertical-align: middle; | |
} | |
& .logo { | |
// float: left; | |
position: relative; | |
display: inline-block; | |
& img { | |
width: 50px; | |
@include border-radius(50%); | |
} | |
& .status-icon { | |
opacity: 0.9; | |
position: absolute; | |
width: 15px; | |
height: 15px; | |
background-color: $flat-red; | |
right: 0; | |
bottom: 0; | |
@include border-radius(50%); | |
&.online { | |
background-color: $flat-green; | |
} | |
} | |
} | |
& .info { | |
display: inline-block; | |
width: 196px; | |
margin-left: 16px; | |
& .status { | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
font-size: 11px; | |
color: $flat-blue; | |
} | |
} | |
} | |
} | |
} | |
} | |
.tabs dd { | |
width: (100% / 3); | |
&:nth-child(2) a { | |
color: $flat-green; | |
} | |
&:nth-child(3) a { | |
color: $flat-red; | |
} | |
&.active a { | |
@include box-shadow(0 5px 0 0 $flat-blue inset); | |
} | |
& a { | |
text-align: center; | |
font-size: 1.575rem; | |
} | |
} |
<link href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" /> | |
<link href="https://cdn.jsdelivr.net/foundation/5.5.0/css/foundation.css" rel="stylesheet" /> | |
<link href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.3.0/animate.min.css" rel="stylesheet" /> |
Display a list of streamers and their status using the twitch.tv API. The user may add/remove/filter streamers from the list.
A Pen by Matthew Kuo on CodePen.