* {box-sizing: border-box;}
body {
margin: 0;
padding: 15px;
color: #444;
background-color: #efefef;
font: normal normal normal 1rem/1.6 Nunito Sans, Helvetica, Arial, sans-serif;
}
.container {
width: 100%;
margin: 0 auto;
}
header {
background-color: #6441A4;
color: #fff;
padding: 15px;
position: relative;
}
header .refresh {
position: absolute;
top: 15px;
right: 15px;
color: #ccc;
cursor: pointer;
}
header .refresh:hover {
color: #fff;
}
header h1 {
margin: 0px;
font-size: 2.5rem;
}
header p {
margin-top: 0px;
font-size: 0.8rem;
text-align: justify;
color: #eee;
}
header a {color: #68efad;}
header a:hover{color: #5fd89e;}
.item a{color: #6441A4;}
.item a:hover{color: #593a94;}
.my-transition {
transition: all 0.35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
-moz-transition: all 0.35s ease-in-out;
}
.box{
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.box:hover{
box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
-webkit-box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
-moz-box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
}
.channels .item {
background-color: #fff;
margin-top: 15px;
padding: 15px;
display: flex;
display: -webkit-flex;
align-items: center;
}
.channels .item.hidden {
display: none;
}
.channels .item .logo-name-wrapper .logo img{
border: 5px solid #6441A4;
border-radius: 50%;
}
.channels .item.offline { border-left: 5px solid #fe4a49;}
.channels .item.online { border-left: 5px solid #68efad;}
.channels .item.closed { border-left: 5px solid #999;}
.channels .item .main {width: calc(100% - 10px);}
.channels .item .remove {opacity: 0;}
.channels .item:hover .remove {opacity: 1;}
.channels .item:hover .remove:hover {cursor: pointer;}
.channels .item .logo-name-wrapper {
display: flex;
display: -webkit-flex;
align-items: center;
}
.channels .item .logo-name-wrapper .name {
padding-left: 15px;
font-size: 0.85rem;
}
.channels .item .status { font-size: 0.85rem; }
.channels .item .logo-name-wrapper img {
width: 60px;
height: 60px;
}
.add form {
width: 100%;
display: flex;
display: -webkit-flex;
align-items: center;
justify-content: space-between;
}
.add .input_wrapper {
padding: 0px 15px;
margin-top: 15px;
height: 60px;
width: calc(100% - 115px);
background-color: #fff;
display: flex;
display: -webkit-flex;
align-items: center;
}
.add input {
padding: 7px 15px;
width: 100%;
}
.add button {
margin-top: 15px;
padding: 0px 15px;
width: 100px;
height: 60px;
color: #444;
background-color: #fff;
border: none;
font-weight: 600;
}
.add button:hover {
cursor: pointer;
}
.add button:focus, .add input:focus {outline: none;}
.add button:active {background-color: #444;}
.add input:active{
outline: none;
background-color: none;
}
.filter {text-align: right;}
.filter .option {
margin-left: 10px;
padding: 2px;
display: inline-block;
}
.filter .option:not(.active):hover {
cursor: pointer;
border-bottom: 3px solid #999;
}
.filter .option.active { border-bottom: 3px solid #fff;}
.filter .online {color: #68efad;}
.filter .offline {color: #fe4a49;}
@media (min-width: 768px) {
.container {
width: 70%;
min-width: 700px;
}
.channels .item .main {
display: flex;
display: -webkit-flex;
align-items: center;
}
.channels .item .main .logo-name-wrapper { flex: 1; }
.channels .item .main .status { flex: 1; }
.channels .item .logo-name-wrapper .name {font-size: 1rem;
}
.channels .item .status { font-size: 1rem; }
.channels .item .logo-name-wrapper img {
width: 80px;
height: 80px;
}
.add button {
width: 120px;
font-size: 1.05rem;
}
.add .input_wrapper { width: calc(100% - 135px); }
}
@media (min-width: 992px) {
.channels .item .main .logo-name-wrapper { flex: 3; }
.channels .item .main .status { flex: 5; }
}
$(document).ready(() => {
const FAVORITE_TWITCHTV_CHANNELS = "favorite-twitchtv-channels";
let default_channels = {
"freecodecamp" : "freecodecamp",
"funfunfunction" : "funfunfunction",
"noopkat" : "noopkat",
"ferossity" : "ferossity",
"kentcdodds" : "kentcdodds",
"radicalfishgames" : "radicalfishgames",
"scinos" : "scinos",
"rthor" : "rthor",
"simalexan" : "simalexan",
"failarmy" : "failarmy"
};
let $container = $(".channels");
let $btnAdd = $(".add button");
let $inpName = $(".add input[type=text]");
let $form = $(".add form");
let $options = $(".filter .option");
let $refresh = $("header .refresh");
let storageHandler = new AppStorage("localStorage");
let channels = getChannels() || default_channels;
setChannels(channels);
render(channels);
$btnAdd.on("click", (event) => {
event.preventDefault();
let channel = $inpName.val().toLowerCase();
$form[0].reset();
if (!channels[channel]) {
channels[channel] = channel;
setChannels(channels);
getChannelInfo(channel);
}
else alert(`Channel '${channel}' already exists!`);
});
$options.on("click", (event) => {
let $target = $(event.target);
if ($target.hasClass("active") == false) {
$target.siblings().removeClass("active");
$target.addClass("active");
if ($target.hasClass("all")) filter("all");
else if ($target.hasClass("online")) filter("online");
else if ($target.hasClass("offline")) filter("offline");
}
});
$refresh.on("click", (event) => {
channels = default_channels;
setChannels(channels);
refreshPage();
});
function refreshPage(){
window.location.href = window.location.href;
}
function render(channels) {
for (let key in channels) {
getChannelInfo(key);
}
}
function filter(state) {
if(state == 'all') {
$container.find(`.item`).removeClass("hidden");
}
else if(state == 'online' || state == 'offline') {
$container.find('.item').addClass("hidden");
$container.find(`.item.${state}`).removeClass("hidden");
}
}
function getChannelInfo(channel) {
$.getJSON(makeURL("channels", channel))
.done(data => onGetChannelInfoSuccess(channel, data))
.fail(error => console.log(error));
}
function onGetChannelInfoSuccess(channel, data) {
if (!data.error) {
if (data.status === null) data.status = "Offline";
let tmpl = itemTemplate(data);
$container.append($(tmpl));
$container.find(`.item[name="${channel}"] .remove`).on("click", onBtnRemoveClicked);
getStreamInfo(channel);
}
else {
delete channels[channel];
setChannels(channels);
alert(`${data.status} - ${data.message}`);
}
}
function onBtnRemoveClicked(event) {
let $item = $(event.target).closest(".item");
let name = $item.attr("name");
delete channels[name];
setChannels(channels);
$item.remove();
}
function getStreamInfo(channel) {
$.getJSON(makeURL("streams", channel))
.done(data => onGetStreamInfoSuccess(channel, data))
.fail(error => console.log(error));
}
function onGetStreamInfoSuccess(channel, data) {
let state;
if (data.stream === null) state = "offline";
else if (data.stream === undefined) state = "closed";
else state = "online";
updateChannelState(channel, state);
}
function updateChannelState(channel, state) {
let $item = $container.find(`.item[name="${channel}"]`);
$item.addClass(state);
}
function makeURL(type, name) {
return `https://wind-bow.gomix.me/twitch-api/${type}/${name}?callback=?`;
};
function setChannels(channels) {
storageHandler.set(FAVORITE_TWITCHTV_CHANNELS, JSON.stringify(channels));
}
function getChannels() {
let channels = storageHandler.get(FAVORITE_TWITCHTV_CHANNELS);
if (channels) return JSON.parse(channels);
}
function itemTemplate(data) {
return `
<div class="item box my-transition" name="${data.name}">
<div class="main">
<div class="logo-name-wrapper">
<div class="logo">
<a href="${data.url}" target="_blank">
<img src="${data.logo}" alt="${data.display_name}'s log">
</a>
</div>
<div class="name">
<a href="${data.url}" target="_blank">${data.display_name}</a>
</div>
</div>
<div class="status">${data.status}</div>
</div>
<i class="fa fa-remove remove my-transition"></i>
</div>
`;
}
});