TS, Less -> JS, CSS
This commit is contained in:
parent
b70f0a891d
commit
8f503a6b10
22 changed files with 515 additions and 811 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,2 +1 @@
|
||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,3 @@
|
||||||
node_modules/*
|
|
||||||
vendor/*
|
|
||||||
public/app.*
|
|
||||||
.apikey
|
.apikey
|
||||||
[Tt]humbs.db
|
[Tt]humbs.db
|
||||||
desktop.ini
|
desktop.ini
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Julian van de Groep <https://flash.moe>
|
Copyright (c) 2016-2021 flashwave <me@flash.moe>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,5 +1,5 @@
|
||||||
# now listening
|
# now listening
|
||||||
A replacement for the old /now page on lastfm profiles!
|
A replacement for the old /now page on last.fm profiles!
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
https://now.flash.moe/#/{username}
|
https://now.flash.moe/#/{username}
|
||||||
|
@ -7,13 +7,5 @@ https://now.flash.moe/#/{username}
|
||||||
## Configuration
|
## Configuration
|
||||||
Create an application [here](https://www.last.fm/api/account/create), take the api and place it in a file called `.apikey` in the root (*not the public dir*).
|
Create an application [here](https://www.last.fm/api/account/create), take the api and place it in a file called `.apikey` in the root (*not the public dir*).
|
||||||
|
|
||||||
## Building
|
## Requirements
|
||||||
To compile the LESS and TypeScript assets you need to have the individual compilers installed,
|
Server side needs PHP 8.0 or newer.
|
||||||
both are available through yarn and can be installed with the following command:
|
|
||||||
`yarn global add less typescript`.
|
|
||||||
|
|
||||||
After that just run `build.sh`.
|
|
||||||
|
|
||||||
The server side uses a PHP script with dependencies to fetch the data without exposing the API key.
|
|
||||||
To install these dependencies you're going to need [composer](https://getcomposer.org/).
|
|
||||||
After installing composer you can simply run `composer install` in the root directory.
|
|
||||||
|
|
19
build.sh
19
build.sh
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# delete old files, using find to avoid errors
|
|
||||||
echo "=> Cleanup"
|
|
||||||
rm -v ./public/app.js
|
|
||||||
rm -v ./public/app.css
|
|
||||||
|
|
||||||
# styles
|
|
||||||
echo
|
|
||||||
echo "=> LESS"
|
|
||||||
lessc --verbose ./src/less/app.less ./public/app.css
|
|
||||||
|
|
||||||
# scripts
|
|
||||||
echo
|
|
||||||
echo "=> TypeScript"
|
|
||||||
tsc \
|
|
||||||
-p ./src/typescript/tsconfig.json \
|
|
||||||
--listFiles \
|
|
||||||
--listEmittedFiles
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"require": {
|
|
||||||
"matto1990/lastfm-api": "^1.2"
|
|
||||||
}
|
|
||||||
}
|
|
70
composer.lock
generated
70
composer.lock
generated
|
@ -1,70 +0,0 @@
|
||||||
{
|
|
||||||
"_readme": [
|
|
||||||
"This file locks the dependencies of your project to a known state",
|
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
|
||||||
"This file is @generated automatically"
|
|
||||||
],
|
|
||||||
"hash": "0447259d6060720f3c189f0fdca5c123",
|
|
||||||
"content-hash": "94d9635fa82a3525da82ed39e8471a8b",
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"name": "matto1990/lastfm-api",
|
|
||||||
"version": "v1.2",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/matto1990/PHP-Last.fm-API.git",
|
|
||||||
"reference": "1cf9a8947bf756beb876d5f8e2af398058805e08"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/matto1990/PHP-Last.fm-API/zipball/1cf9a8947bf756beb876d5f8e2af398058805e08",
|
|
||||||
"reference": "1cf9a8947bf756beb876d5f8e2af398058805e08",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.3"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"php": ">=5.3.3",
|
|
||||||
"phpunit/phpunit": "~4.8"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"LastFmApi\\": "src/lastfmapi/",
|
|
||||||
"Tests\\": "tests"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Marcos",
|
|
||||||
"email": "devilcius@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Matt",
|
|
||||||
"email": "matt@oakes.ws"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Last.fm webservice client",
|
|
||||||
"homepage": "https://github.com/matto1990/PHP-Last.fm-API",
|
|
||||||
"keywords": [
|
|
||||||
"api",
|
|
||||||
"last.fm",
|
|
||||||
"webservice client"
|
|
||||||
],
|
|
||||||
"time": "2016-03-17 16:41:24"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"packages-dev": [],
|
|
||||||
"aliases": [],
|
|
||||||
"minimum-stability": "stable",
|
|
||||||
"stability-flags": [],
|
|
||||||
"prefer-stable": false,
|
|
||||||
"prefer-lowest": false,
|
|
||||||
"platform": [],
|
|
||||||
"platform-dev": []
|
|
||||||
}
|
|
3
public/get-xml.php
Normal file
3
public/get-xml.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
$format = 'xml';
|
||||||
|
require __DIR__ . '/get.php';
|
163
public/get.php
163
public/get.php
|
@ -1,31 +1,148 @@
|
||||||
<?php
|
<?php
|
||||||
use LastFmApi\Api\AuthApi;
|
define('FNP_API_KEY', __DIR__ . '/../.apikey');
|
||||||
use LastFmApi\Api\UserApi;
|
define('FNP_API_URL', 'https://ws.audioscrobbler.com');
|
||||||
|
|
||||||
function view($object)
|
define('FNP_FMT_JSON', 'json');
|
||||||
{
|
define('FNP_FMT_XML', 'xml');
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
define('FNP_FMTS', [
|
||||||
return json_encode($object, JSON_NUMERIC_CHECK);
|
FNP_FMT_JSON => 'application/json',
|
||||||
|
FNP_FMT_XML => 'application/xml',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(!isset($format))
|
||||||
|
$format = (string)(filter_input(INPUT_GET, 'f') ?? 'json');
|
||||||
|
$pretty = !empty($_GET['p']);
|
||||||
|
|
||||||
|
if($format !== 'json' && $format !== 'xml') {
|
||||||
|
http_response_code(400);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists('../vendor/autoload.php')) {
|
$charset = preg_match('#MSIE#i', $_SERVER['HTTP_USER_AGENT'] ?? '') ? '' : 'utf-8';
|
||||||
die(view(['error' => 'Please run "composer install" in the main directory first!']));
|
|
||||||
|
header('Content-Type: ' . FNP_FMTS[$format] . (empty($charset) ? '' : ('; charset=' . $charset)));
|
||||||
|
|
||||||
|
function errorResponse(string $text, int $code = -1): void {
|
||||||
|
global $format, $charset;
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
switch($format) {
|
||||||
|
case FNP_FMT_JSON:
|
||||||
|
echo json_encode(['error' => $text, 'code' => $code]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Has strange formatting, but XML mode exists for backwards compatibility with an unreleased variation of get.php.
|
||||||
|
case FNP_FMT_XML:
|
||||||
|
$document = new DOMDocument('1.0', $charset);
|
||||||
|
$tracks = $document->appendChild($document->createElement('Tracks'));
|
||||||
|
$track = $tracks->appendChild($document->createElement('Track'));
|
||||||
|
$track->setAttribute('error', $text);
|
||||||
|
$track->setAttribute('code', $code);
|
||||||
|
echo $document->saveXML();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once '../vendor/autoload.php';
|
if(!is_file(FNP_API_KEY))
|
||||||
|
errorResponse('API key file missing! Create a file called ".apikey" in the root directory and place your last.fm api key in there.');
|
||||||
|
|
||||||
if (!file_exists('../.apikey')) {
|
$apiKey = trim(file_get_contents(FNP_API_KEY));
|
||||||
die(view(['error' => 'API key file missing! Create a file called ".apikey" in the root directory and place your last.fm api key in there.']));
|
$userName = (string)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
$curl = curl_init(FNP_API_URL . '/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' . $apiKey . '&user=' . rawurlencode($userName));
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_AUTOREFERER => false,
|
||||||
|
CURLOPT_COOKIESESSION => false,
|
||||||
|
CURLOPT_VERBOSE => false,
|
||||||
|
CURLOPT_FAILONERROR => false,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_TCP_NODELAY => true,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TCP_FASTOPEN => true,
|
||||||
|
CURLOPT_VERBOSE => false,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 2,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||||
|
CURLOPT_TIMEOUT => 2,
|
||||||
|
CURLOPT_USERAGENT => 'NowListening v2 (' . $_SERVER['HTTP_HOST'] . ')',
|
||||||
|
]);
|
||||||
|
$response = json_decode(curl_exec($curl));
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
if($response === null)
|
||||||
|
errorResponse('API request failed.');
|
||||||
|
if(isset($response->message))
|
||||||
|
errorResponse($response->message, $response->error ?? -1);
|
||||||
|
|
||||||
|
switch($format) {
|
||||||
|
case FNP_FMT_JSON:
|
||||||
|
$tracks = [];
|
||||||
|
|
||||||
|
if(!empty($response->recenttracks?->track))
|
||||||
|
foreach($response->recenttracks->track as $recentTrack) {
|
||||||
|
$tracks[] = $track = new stdClass;
|
||||||
|
$track->name = $recentTrack->name;
|
||||||
|
if(!empty($recentTrack->{"@attr"}?->nowplaying))
|
||||||
|
$track->nowplaying = true;
|
||||||
|
$track->mbid = $recentTrack->mbid;
|
||||||
|
$track->url = $recentTrack->url;
|
||||||
|
$track->date = $recentTrack->date?->uts ?? '';
|
||||||
|
$track->streamable = $recentTrack->streamable;
|
||||||
|
|
||||||
|
$track->artist = new stdClass;
|
||||||
|
$track->artist->name = $recentTrack->artist?->{"#text"} ?? '';
|
||||||
|
$track->artist->mbid = $recentTrack->artist?->mbid ?? '';
|
||||||
|
|
||||||
|
$track->album = new stdClass;
|
||||||
|
$track->album->name = $recentTrack->album?->{"#text"};
|
||||||
|
$track->album->mbid = $recentTrack->album?->mbid ?? '';
|
||||||
|
|
||||||
|
$track->images = new stdClass;
|
||||||
|
foreach($recentTrack->image as $trackImage)
|
||||||
|
$track->images->{$trackImage->size} = $trackImage->{"#text"};
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($tracks, $pretty ? JSON_PRETTY_PRINT : 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FNP_FMT_XML:
|
||||||
|
$document = new DOMDocument('1.0', $charset);
|
||||||
|
$document->formatOutput = $pretty;
|
||||||
|
$tracks = $document->appendChild($document->createElement('Tracks'));
|
||||||
|
|
||||||
|
if(!empty($response->recenttracks?->track))
|
||||||
|
foreach($response->recenttracks->track as $recentTrack) {
|
||||||
|
$elem = $tracks->appendChild($document->createElement('Track'));
|
||||||
|
|
||||||
|
$elem->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->name)));
|
||||||
|
$elem->appendChild($document->createElement('Mbid', $recentTrack->mbid));
|
||||||
|
$elem->appendChild($document->createElement('Url', htmlspecialchars($recentTrack->url)));
|
||||||
|
$elem->appendChild($document->createElement('Date', empty($recentTrack->date?->uts) ? '' : date('c', $recentTrack->date->uts)));
|
||||||
|
$elem->appendChild($document->createElement('Streamable', empty($recentTrack->streamable) ? '0' : '1'));
|
||||||
|
$elem->appendChild($document->createElement('IsPlaying', empty($recentTrack->{"@attr"}?->nowplaying) ? '0' : '1'));
|
||||||
|
|
||||||
|
$artist = $elem->appendChild($document->createElement('Artist'));
|
||||||
|
$artist->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->artist?->{"#text"} ?? '')));
|
||||||
|
$artist->appendChild($document->createElement('Mbid', $recentTrack->artist?->mbid ?? ''));
|
||||||
|
|
||||||
|
$album = $elem->appendChild($document->createElement('Album'));
|
||||||
|
$album->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->album?->{"#text"} ?? '')));
|
||||||
|
$album->appendChild($document->createElement('Mbid', $recentTrack->album?->mbid ?? ''));
|
||||||
|
|
||||||
|
$images = $elem->appendChild($document->createElement('Images'));
|
||||||
|
foreach($recentTrack->image as $trackImage) {
|
||||||
|
$name = $trackImage->size;
|
||||||
|
if($name === 'extralarge')
|
||||||
|
$name = 'ExtraLarge';
|
||||||
|
else
|
||||||
|
$name = ucfirst($name);
|
||||||
|
$images->appendChild($document->createElement($name, htmlspecialchars($trackImage->{"#text"})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $document->saveXML();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$api_key = trim(file_get_contents('../.apikey'));
|
|
||||||
|
|
||||||
$auth = new AuthApi('setsession', ['apiKey' => $api_key]);
|
|
||||||
$user = new UserApi($auth);
|
|
||||||
$now = $user->getRecentTracks(['user' => (isset($_GET['u']) ? $_GET['u'] : ''), 'limit' => '1']);
|
|
||||||
|
|
||||||
if ($now === false) {
|
|
||||||
$now = ['error' => 'User not found.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo view($now);
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
<title>Now Listening</title>
|
<title>Now Listening</title>
|
||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no" />
|
||||||
<link href="https://fonts.googleapis.com/css?family=Exo+2:400,400italic,200,200italic" rel="stylesheet" type="text/css">
|
<link href="https://fonts.googleapis.com/css?family=Exo+2:400,400italic,200,200italic" rel="stylesheet" type="text/css" />
|
||||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="/app.css" type="text/css" rel="stylesheet">
|
<link href="/now.css" type="text/css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="background" id="background"></div>
|
<div class="background" id="background"></div>
|
||||||
|
@ -40,6 +40,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/app.js" type="text/javascript"></script>
|
<script src="/now.js" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
176
public/now.css
Normal file
176
public/now.css
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: url('resources/grid.png') transparent;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
font: 12px/20px 'Exo 2', sans-serif;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.container__index,
|
||||||
|
.container__user {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container__index {
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(17, 17, 17, 0.8);
|
||||||
|
box-shadow: 0 1px 4px #111;
|
||||||
|
padding: 6px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container__user {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
box-shadow: 0 1px 4px #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
background: none no-repeat center / cover #000;
|
||||||
|
z-index: -1;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
transition: background-color 2.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__title {
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__form {
|
||||||
|
margin: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 1px 4px #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__username,
|
||||||
|
.index__submit {
|
||||||
|
border: 1px solid #111;
|
||||||
|
color: #fff;
|
||||||
|
padding: 2px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__username {
|
||||||
|
background: rgba(17, 17, 17, 0.5);
|
||||||
|
border-right: 0;
|
||||||
|
font-family: 'Exo 2', sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__submit {
|
||||||
|
border-left: 0;
|
||||||
|
background: #111;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index__dev {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.cover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover__image {
|
||||||
|
background: none no-repeat center / cover rgba(17, 17, 17, 0.8);
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
background: rgba(17, 17, 17, 0.8);
|
||||||
|
padding: 6px 8px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 601px) {
|
||||||
|
.info {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__title {
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__artist {
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__flags {
|
||||||
|
line-height: 1.5em;
|
||||||
|
font-size: 2em;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__user {
|
||||||
|
align-self: end;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__user-back {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info__user-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
184
public/now.js
Normal file
184
public/now.js
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
var fnp = {
|
||||||
|
defaultCoverFnp: '/resources/no-cover.png',
|
||||||
|
defaultCoverLfm: 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',
|
||||||
|
watch: {
|
||||||
|
userName: null,
|
||||||
|
interval: 0,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
mode: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.goIndex = function() {
|
||||||
|
location.hash = '';
|
||||||
|
};
|
||||||
|
fnp.switchUser = function(userName) {
|
||||||
|
location.hash = '#/' + encodeURIComponent((userName ?? '').toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.updateHash = function() {
|
||||||
|
var userName = decodeURIComponent(location.hash.substring(2));
|
||||||
|
|
||||||
|
if(userName.length > 0) {
|
||||||
|
this.watch.start(userName, function(response) {
|
||||||
|
if(response.error) {
|
||||||
|
this.goIndex();
|
||||||
|
alert(response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cover = response[0].images.extralarge;
|
||||||
|
console.log(cover);
|
||||||
|
if(cover.length < 1 || cover === this.defaultCoverLfm)
|
||||||
|
cover = this.defaultCoverFnp;
|
||||||
|
console.log(cover);
|
||||||
|
|
||||||
|
this.ui.setInfo(cover, response[0].name, response[0].artist.name, userName, response[0].nowplaying || false);
|
||||||
|
this.ui.goUser();
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.watch.stop();
|
||||||
|
this.ui.goIndex();
|
||||||
|
this.ui.setBackgroundFade();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.watch.start = function(userName, callback) {
|
||||||
|
this.stop();
|
||||||
|
this.userName = userName = (userName || '').toString();
|
||||||
|
this.fetch(function(response) {
|
||||||
|
if(response.error)
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
fnp.watch.stop = function() {
|
||||||
|
if(this.interval !== 0) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userName = null;
|
||||||
|
};
|
||||||
|
fnp.watch.fetch = function(callback) {
|
||||||
|
if(typeof callback !== 'function')
|
||||||
|
return;
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if(xhr.readyState !== 4)
|
||||||
|
return;
|
||||||
|
callback(JSON.parse(xhr.responseText));
|
||||||
|
};
|
||||||
|
xhr.open('GET', '/get.php?u=' + encodeURIComponent(this.userName));
|
||||||
|
xhr.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.ui.setMode = function(modeName) {
|
||||||
|
modeName = (modeName || '').toString();
|
||||||
|
if(this.mode === modeName)
|
||||||
|
return;
|
||||||
|
this.mode = modeName;
|
||||||
|
|
||||||
|
for(var i = 0; i < this.modes.length; ++i) {
|
||||||
|
var mode = this.modes[i];
|
||||||
|
mode.elem.classList[mode.name === modeName ? 'remove' : 'add']('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fnp.ui.goIndex = function() {
|
||||||
|
this.setMode('index');
|
||||||
|
};
|
||||||
|
fnp.ui.goUser = function() {
|
||||||
|
this.setMode('user');
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.ui.setBackground = function(bgPath) {
|
||||||
|
if(bgPath === '::fade') {
|
||||||
|
this.setBackgroundFade();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.iColourFade !== 0) {
|
||||||
|
clearInterval(this.iColourFade);
|
||||||
|
this.iColourFade = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eBg.style.backgroundColor = null;
|
||||||
|
this.eBg.style.backgroundImage = 'url(\'' + (bgPath || '').toString() + '\')';
|
||||||
|
};
|
||||||
|
fnp.ui.setBackgroundFade = function() {
|
||||||
|
if(this.iColourFade !== 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.eBg.style.backgroundImage = null;
|
||||||
|
|
||||||
|
var fader = function() {
|
||||||
|
var colour = Math.floor(Math.random() * 0xFFFFFF).toString(16);
|
||||||
|
|
||||||
|
if(colour.length !== 6 && colour.length !== 3)
|
||||||
|
colour = '000000'.substring(colour.length) + colour;
|
||||||
|
|
||||||
|
this.eBg.style.backgroundColor = '#' + colour;
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.iColourFade = setInterval(fader, 2000);
|
||||||
|
fader();
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.ui.setInfo = function(cover, title, artist, user, now) {
|
||||||
|
this.setBackground(cover);
|
||||||
|
this.eInfoCover.style.backgroundImage = 'url(\'' + (cover || '').toString() + '\')';
|
||||||
|
this.eInfoTitle.textContent = (title || '').toString();
|
||||||
|
this.eInfoArtist.textContent = (artist || '').toString();
|
||||||
|
this.eInfoUser.textContent = (user || '').toString();
|
||||||
|
this.eInfoFlags.innerHTML = '';
|
||||||
|
|
||||||
|
if(now) {
|
||||||
|
var nowIcon = document.createElement('span');
|
||||||
|
nowIcon.className = 'fa fa-music';
|
||||||
|
nowIcon.title = 'Now playing';
|
||||||
|
this.eInfoFlags.appendChild(nowIcon);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
fnp.ui.eIndex = document.getElementById('index');
|
||||||
|
fnp.ui.eUser = document.getElementById('user');
|
||||||
|
fnp.ui.eBg = document.getElementById('background');
|
||||||
|
fnp.ui.eFormUser = document.getElementById('username');
|
||||||
|
fnp.ui.eFormSend = document.getElementById('submit');
|
||||||
|
fnp.ui.eInfoCover = document.getElementById('np_cover');
|
||||||
|
fnp.ui.eInfoTitle = document.getElementById('np_title');
|
||||||
|
fnp.ui.eInfoArtist = document.getElementById('np_artist');
|
||||||
|
fnp.ui.eInfoUser = document.getElementById('np_user');
|
||||||
|
fnp.ui.eInfoBack = document.getElementById('np_back');
|
||||||
|
fnp.ui.eInfoFlags = document.getElementById('np_flags');
|
||||||
|
fnp.ui.iColourFade = 0;
|
||||||
|
fnp.ui.modes = [
|
||||||
|
{ name: 'index', elem: fnp.ui.eIndex },
|
||||||
|
{ name: 'user', elem: fnp.ui.eUser },
|
||||||
|
];
|
||||||
|
|
||||||
|
fnp.ui.eInfoBack.onclick = function() {
|
||||||
|
fnp.goIndex();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onhashchange = function() {
|
||||||
|
fnp.updateHash();
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.ui.eFormSend.onclick = function() {
|
||||||
|
fnp.switchUser(fnp.ui.eFormUser.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.ui.eFormUser.onkeydown = function(ev) {
|
||||||
|
if(typeof ev.key === 'undefined')
|
||||||
|
ev.key = ev.keyCode === 13 ? 'Enter' : '?';
|
||||||
|
|
||||||
|
if(ev.key === 'Enter' || ev.key === 'NumpadEnter')
|
||||||
|
fnp.switchUser(fnp.ui.eFormUser.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
fnp.updateHash();
|
||||||
|
};
|
|
@ -1,174 +0,0 @@
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: url('resources/grid.png') transparent;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
color: #fff;
|
|
||||||
font: 12px/20px "Exo 2", sans-serif;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
&__index,
|
|
||||||
&__user {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__index {
|
|
||||||
text-align: center;
|
|
||||||
background: fade(#111, 80%);
|
|
||||||
box-shadow: 0 1px 4px #111;
|
|
||||||
padding: 6px 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__user {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
box-shadow: 0 1px 4px #111;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.background {
|
|
||||||
background: none no-repeat center / cover #000;
|
|
||||||
z-index: -1;
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
transition: background-color 2.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index {
|
|
||||||
&__title {
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 3em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__form {
|
|
||||||
margin: 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
box-shadow: 0 1px 4px #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__username,
|
|
||||||
&__submit {
|
|
||||||
border: 1px solid #111;
|
|
||||||
color: #fff;
|
|
||||||
padding: 2px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__username {
|
|
||||||
background: fade(#111, 50%);
|
|
||||||
border-right: 0;
|
|
||||||
font-family: "Exo 2", sans-serif;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__submit {
|
|
||||||
border-left: 0;
|
|
||||||
background: #111;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dev {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover {
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image {
|
|
||||||
background: none no-repeat center / cover fade(#111, 80%);
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
background: fade(#111, 80%);
|
|
||||||
padding: 6px 8px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
|
|
||||||
@media (min-width: 601px) {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 3em;
|
|
||||||
line-height: 1.2em;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__artist {
|
|
||||||
font-size: 1.5em;
|
|
||||||
line-height: 1.2em;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__flags {
|
|
||||||
line-height: 1.5em;
|
|
||||||
font-size: 2em;
|
|
||||||
flex-grow: 1; // fill space
|
|
||||||
}
|
|
||||||
|
|
||||||
&__user {
|
|
||||||
align-self: end;
|
|
||||||
|
|
||||||
display: inline-flex;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&-back {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-name {
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export class AJAX
|
|
||||||
{
|
|
||||||
// XMLHTTPRequest container
|
|
||||||
private Request: XMLHttpRequest;
|
|
||||||
private Callbacks: any;
|
|
||||||
private Headers: any;
|
|
||||||
private URL: string;
|
|
||||||
private Send: string = null;
|
|
||||||
private Asynchronous: boolean = true;
|
|
||||||
|
|
||||||
// Prepares the XMLHttpRequest and stuff
|
|
||||||
constructor(async: boolean = true) {
|
|
||||||
this.Request = new XMLHttpRequest();
|
|
||||||
this.Callbacks = new Object();
|
|
||||||
this.Headers = new Object();
|
|
||||||
this.Asynchronous = async;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start
|
|
||||||
public Start(method: HTTPMethod, avoidCache: boolean = false): void {
|
|
||||||
// Open the connection
|
|
||||||
this.Request.open(HTTPMethod[method], this.URL + (avoidCache ? "?no-cache=" + Date.now() : ""), this.Asynchronous);
|
|
||||||
|
|
||||||
// Set headers
|
|
||||||
this.PrepareHeaders();
|
|
||||||
|
|
||||||
// Watch the ready state
|
|
||||||
this.Request.onreadystatechange = () => {
|
|
||||||
// Only invoke when complete
|
|
||||||
if (this.Request.readyState === 4) {
|
|
||||||
// Check if a callback if present
|
|
||||||
if ((typeof this.Callbacks[this.Request.status]).toLowerCase() === 'function') {
|
|
||||||
this.Callbacks[this.Request.status]();
|
|
||||||
} else { // Else check if there's a generic fallback present
|
|
||||||
if ((typeof this.Callbacks['0']).toLowerCase() === 'function') {
|
|
||||||
// Call that
|
|
||||||
this.Callbacks['0']();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Request.send(this.Send);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop
|
|
||||||
public Stop(): void {
|
|
||||||
this.Request = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add post data
|
|
||||||
public SetSend(data: any): void {
|
|
||||||
// Storage array
|
|
||||||
var store: Array<string> = new Array<string>();
|
|
||||||
|
|
||||||
// Iterate over the object and them in the array with an equals sign inbetween
|
|
||||||
for (var item in data) {
|
|
||||||
store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign to send
|
|
||||||
this.Send = store.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set raw post
|
|
||||||
public SetRawSend(data: string): void {
|
|
||||||
this.Send = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get response
|
|
||||||
public Response(): string {
|
|
||||||
return this.Request.responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all headers
|
|
||||||
public ResponseHeaders(): string {
|
|
||||||
return this.Request.getAllResponseHeaders();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a header
|
|
||||||
public ResponseHeader(name: string): string {
|
|
||||||
return this.Request.getResponseHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set charset
|
|
||||||
public ContentType(type: string, charset: string = null): void {
|
|
||||||
this.AddHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a header
|
|
||||||
public AddHeader(name: string, value: string): void {
|
|
||||||
// Attempt to remove a previous instance
|
|
||||||
this.RemoveHeader(name);
|
|
||||||
|
|
||||||
// Add the new header
|
|
||||||
this.Headers[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a header
|
|
||||||
public RemoveHeader(name: string): void {
|
|
||||||
if ((typeof this.Headers[name]).toLowerCase() !== 'undefined') {
|
|
||||||
delete this.Headers[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare Request headers
|
|
||||||
public PrepareHeaders(): void {
|
|
||||||
for (var header in this.Headers) {
|
|
||||||
this.Request.setRequestHeader(header, this.Headers[header]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a callback
|
|
||||||
public AddCallback(status: number, callback: Function): void {
|
|
||||||
// Attempt to remove previous instances
|
|
||||||
this.RemoveCallback(status);
|
|
||||||
|
|
||||||
// Add the new callback
|
|
||||||
this.Callbacks[status] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a callback
|
|
||||||
public RemoveCallback(status: number): void {
|
|
||||||
// Delete the callback if present
|
|
||||||
if ((typeof this.Callbacks[status]).toLowerCase() === 'function') {
|
|
||||||
delete this.Callbacks[status];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the URL
|
|
||||||
public SetUrl(url: string): void {
|
|
||||||
this.URL = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export enum Background
|
|
||||||
{
|
|
||||||
IMAGE,
|
|
||||||
COLOURFADE
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export class DOM
|
|
||||||
{
|
|
||||||
// Block Element Modifier class name generator
|
|
||||||
public static BEM(block: string, element: string = null, modifiers: string[] = [], firstModifierOnly: boolean = false): string
|
|
||||||
{
|
|
||||||
var className: string = "";
|
|
||||||
|
|
||||||
if (firstModifierOnly && modifiers.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
className += block;
|
|
||||||
|
|
||||||
if (element !== null) {
|
|
||||||
className += "__" + element;
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseName: string = className;
|
|
||||||
|
|
||||||
for (var _i in modifiers) {
|
|
||||||
if (firstModifierOnly) {
|
|
||||||
return baseName + "--" + modifiers[_i];
|
|
||||||
}
|
|
||||||
|
|
||||||
className += " " + baseName + "--" + modifiers[_i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return className;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for creating an element with a class and string
|
|
||||||
public static Element(name: string, className: string = null, id: string = null): HTMLElement {
|
|
||||||
var element = document.createElement(name);
|
|
||||||
|
|
||||||
if (className !== null) {
|
|
||||||
element.className = className;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id !== null) {
|
|
||||||
element.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for textnode
|
|
||||||
public static Text(text: string): Text {
|
|
||||||
return document.createTextNode(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for getElementById (i'm lazy)
|
|
||||||
public static ID(id: string): HTMLElement {
|
|
||||||
return document.getElementById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for removing an element
|
|
||||||
public static Remove(element: HTMLElement): void {
|
|
||||||
element.parentNode.removeChild(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for the first element of getElementsByClassName
|
|
||||||
public static Class(className: string): NodeListOf<Element> {
|
|
||||||
return <NodeListOf<Element>>document.getElementsByClassName(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for prepending
|
|
||||||
public static Prepend(target: HTMLElement, element: HTMLElement | Text): void {
|
|
||||||
if (target.children.length) {
|
|
||||||
target.insertBefore(element, target.firstChild);
|
|
||||||
} else {
|
|
||||||
this.Append(target, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shorthand for appending
|
|
||||||
public static Append(target: HTMLElement, element: HTMLElement | Text): void {
|
|
||||||
target.appendChild(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting all classes of an element
|
|
||||||
public static ClassNames(target: HTMLElement): string[] {
|
|
||||||
var className: string = target.className,
|
|
||||||
classes: string[] = [];
|
|
||||||
|
|
||||||
if (className.length > 1) {
|
|
||||||
classes = className.split(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
return classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding classes to an element
|
|
||||||
public static AddClass(target: HTMLElement, classes: string[]): void {
|
|
||||||
for (var _i in classes) {
|
|
||||||
var current: string[] = this.ClassNames(target),
|
|
||||||
index: number = current.indexOf(classes[_i]);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
current.push(classes[_i]);
|
|
||||||
|
|
||||||
target.className = current.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removing classes
|
|
||||||
public static RemoveClass(target: HTMLElement, classes: string[]): void {
|
|
||||||
for (var _i in classes) {
|
|
||||||
var current: string[] = this.ClassNames(target),
|
|
||||||
index: number = current.indexOf(classes[_i]);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
current.splice(index, 1);
|
|
||||||
|
|
||||||
target.className = current.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Clone(subject: HTMLElement): HTMLElement {
|
|
||||||
return (<HTMLElement>subject.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Query(query: string): NodeListOf<Element> {
|
|
||||||
return document.querySelectorAll(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export enum HTTPMethod
|
|
||||||
{
|
|
||||||
GET,
|
|
||||||
HEAD,
|
|
||||||
POST,
|
|
||||||
PUT,
|
|
||||||
DELETE
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
window.addEventListener("load", () => {
|
|
||||||
NP.UI.RegisterHooks();
|
|
||||||
NP.UI.Update();
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export enum Mode
|
|
||||||
{
|
|
||||||
INDEX,
|
|
||||||
USER
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export class UI
|
|
||||||
{
|
|
||||||
private static IndexElem: HTMLDivElement = <HTMLDivElement>DOM.ID('index');
|
|
||||||
private static UserElem: HTMLDivElement = <HTMLDivElement>DOM.ID('user');
|
|
||||||
|
|
||||||
private static BGElem: HTMLDivElement = <HTMLDivElement>DOM.ID('background');
|
|
||||||
|
|
||||||
private static FormUsername: HTMLInputElement = <HTMLInputElement>DOM.ID('username');
|
|
||||||
private static FormSubmit: HTMLButtonElement = <HTMLButtonElement>DOM.ID('submit');
|
|
||||||
|
|
||||||
private static InfoCover: HTMLDivElement = <HTMLDivElement>DOM.ID('np_cover');
|
|
||||||
private static InfoTitle: HTMLDivElement = <HTMLDivElement>DOM.ID('np_title');
|
|
||||||
private static InfoArtist: HTMLDivElement = <HTMLDivElement>DOM.ID('np_artist');
|
|
||||||
private static InfoUser: HTMLDivElement = <HTMLDivElement>DOM.ID('np_user');
|
|
||||||
private static InfoBack: HTMLDivElement = <HTMLDivElement>DOM.ID('np_back');
|
|
||||||
private static InfoFlags: HTMLDivElement = <HTMLDivElement>DOM.ID('np_flags');
|
|
||||||
|
|
||||||
private static ColourFadeInterval: number = null;
|
|
||||||
|
|
||||||
public static Mode(mode: Mode): void {
|
|
||||||
switch (mode) {
|
|
||||||
case Mode.INDEX:
|
|
||||||
DOM.AddClass(this.UserElem, ['hidden']);
|
|
||||||
DOM.RemoveClass(this.IndexElem, ['hidden']);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Mode.USER:
|
|
||||||
DOM.AddClass(this.IndexElem, ['hidden']);
|
|
||||||
DOM.RemoveClass(this.UserElem, ['hidden']);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Background(mode: Background, property: string = null): void {
|
|
||||||
switch (mode) {
|
|
||||||
case Background.COLOURFADE:
|
|
||||||
if (this.ColourFadeInterval !== null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.BGElem.style.backgroundImage = null;
|
|
||||||
|
|
||||||
var fader: Function = () => {
|
|
||||||
var colour: string = Math.floor(Math.random() * 16777215).toString(16);
|
|
||||||
|
|
||||||
if (colour.length !== 6 && colour.length !== 3) {
|
|
||||||
colour = "000000".substring(colour.length) + colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.BGElem.style.backgroundColor = "#" + colour;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ColourFadeInterval = setInterval(fader, 2000);
|
|
||||||
fader.call(this);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Background.IMAGE:
|
|
||||||
if (property === null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ColourFadeInterval !== null) {
|
|
||||||
clearInterval(this.ColourFadeInterval);
|
|
||||||
this.ColourFadeInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.BGElem.style.backgroundColor = null;
|
|
||||||
this.BGElem.style.backgroundImage = "url('{0}')".replace('{0}', property);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SetInfo(cover: string, title: string, artist: string, user: string, now: boolean = false): void {
|
|
||||||
this.Background(Background.IMAGE, cover);
|
|
||||||
this.InfoCover.style.backgroundImage = "url('{0}')".replace('{0}', cover);
|
|
||||||
this.InfoTitle.innerText = title;
|
|
||||||
this.InfoArtist.innerText = artist;
|
|
||||||
this.InfoUser.innerText = user;
|
|
||||||
this.InfoFlags.innerHTML = '';
|
|
||||||
|
|
||||||
if (now) {
|
|
||||||
var nowIcon: HTMLSpanElement = DOM.Element('span', 'fa fa-music');
|
|
||||||
nowIcon.title = 'Now playing';
|
|
||||||
this.InfoFlags.appendChild(nowIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Update(): void {
|
|
||||||
var user: string = location.hash.substring(2);
|
|
||||||
|
|
||||||
if (user.length > 0) {
|
|
||||||
Watcher.Start(user);
|
|
||||||
UI.Mode(Mode.USER);
|
|
||||||
} else {
|
|
||||||
Watcher.Stop();
|
|
||||||
UI.Mode(Mode.INDEX);
|
|
||||||
UI.Background(Background.COLOURFADE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegisterHooks(): void {
|
|
||||||
UI.InfoBack.addEventListener('click', () => {
|
|
||||||
location.hash = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
var enter: Function = () => {
|
|
||||||
location.hash = '#/' + UI.FormUsername.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('hashchange', () => {
|
|
||||||
UI.Update();
|
|
||||||
});
|
|
||||||
|
|
||||||
UI.FormSubmit.addEventListener('click', () => {
|
|
||||||
enter.call(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
UI.FormUsername.addEventListener('keydown', (ev) => {
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
enter.call(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
namespace NP
|
|
||||||
{
|
|
||||||
export class Watcher
|
|
||||||
{
|
|
||||||
private static Fetcher: AJAX = null;
|
|
||||||
private static CheckIntervalId: number = null;
|
|
||||||
private static CheckTimeout: number = 15 * 1000;
|
|
||||||
private static GetPath: string = "/get.php";
|
|
||||||
private static User: string = null;
|
|
||||||
|
|
||||||
public static Start(user: string): void {
|
|
||||||
if (this.Fetcher !== null) {
|
|
||||||
this.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.User = user;
|
|
||||||
this.Fetcher = new AJAX;
|
|
||||||
|
|
||||||
this.Fetcher.AddCallback(200, () => {
|
|
||||||
var data: any = JSON.parse(Watcher.Fetcher.Response());
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if ((typeof data.error).toLowerCase() !== 'undefined') {
|
|
||||||
Watcher.Stop();
|
|
||||||
UI.Mode(Mode.INDEX);
|
|
||||||
NP.UI.Background(NP.Background.COLOURFADE);
|
|
||||||
alert(data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.Mode(Mode.USER);
|
|
||||||
|
|
||||||
var image: string = data[0].images.large.length < 1 ? '/resources/no-cover.png' : data[0].images.large.replace('/174s/', '/300s/'),
|
|
||||||
now: boolean = (typeof data[0].nowplaying).toLowerCase() === 'undefined' ? false : data[0].nowplaying;
|
|
||||||
|
|
||||||
UI.SetInfo(image, data[0].name, data[0].artist.name, this.User, now);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.Check();
|
|
||||||
|
|
||||||
this.CheckIntervalId = setInterval(this.Check, this.CheckTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Stop(): void {
|
|
||||||
clearInterval(this.CheckIntervalId);
|
|
||||||
this.CheckIntervalId = null;
|
|
||||||
this.Fetcher = null;
|
|
||||||
this.User = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Check(): void {
|
|
||||||
Watcher.Fetcher.SetUrl(Watcher.GetPath + "?u=" + Watcher.User);
|
|
||||||
Watcher.Fetcher.Start(HTTPMethod.GET);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"removeComments": true,
|
|
||||||
"outFile": "../../public/app.js"
|
|
||||||
},
|
|
||||||
"filesGlob": [
|
|
||||||
"**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in a new issue