Display a list of streamers and their status using the twitch.tv API. The user may add/remove/filter streamers from the list.
-
-
Save adryserage/16da71010260a7316235 to your computer and use it in GitHub Desktop.
Twitch Streamers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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"]; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment