Skip to content

Instantly share code, notes, and snippets.

Created March 8, 2016 15:07
Show Gist options
  • Save anonymous/41f3e3d88919add33078 to your computer and use it in GitHub Desktop.
Save anonymous/41f3e3d88919add33078 to your computer and use it in GitHub Desktop.
Twitch Streamers
<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">&times;</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="&#xf067;"/>
</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" />

Twitch Streamers

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.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment