Add list views and additional configuration options

This commit is contained in:
Timothy Warren 2015-06-16 11:11:35 -04:00
parent aedfc743c6
commit 9afe5379aa
28 changed files with 1904 additions and 392 deletions

View File

@ -1,4 +1,7 @@
<?php
/**
* Base API Model
*/
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;

View File

@ -1,4 +1,7 @@
<?php
/**
* Base Controller
*/
/**
* Base class for controllers, defines output methods
@ -24,26 +27,30 @@ class BaseController {
* Output a template to HTML, using the provided data
*
* @param string $template
* @param array/object $data
* @param array|object $data
* @return void
*/
public function outputHTML($template, $data=[])
{
global $router;
global $router, $defaultHandler;
$route = $router->get_route();
$data['route_path'] = ($route) ? $router->get_route()->path : "";
$path = _dir(APP_DIR, 'views', "{$template}.php");
$defaultHandler->addDataTable('Template Data', $data);
if ( ! is_file($path))
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
if ( ! is_file($template_path))
{
throw new Exception("Invalid template : {$path}");
die();
}
ob_start();
extract($data);
include _dir(APP_DIR, 'views', 'header.php');
include $path;
include $template_path;
include _dir(APP_DIR, 'views', 'footer.php');
$buffer = ob_get_contents();
ob_end_clean();
@ -55,7 +62,7 @@ class BaseController {
/**
* Output json with the proper content type
*
* @param mixed data
* @param mixed $data
* @return void
*/
public function outputJSON($data)
@ -68,5 +75,27 @@ class BaseController {
header("Content-type: application/json");
echo $data;
}
/**
* Redirect to the selected page
*
* @param string $url
* @param int $code
* @return void
*/
public function redirect($url, $code, $type="anime")
{
$url = full_url($url, $type);
$codes = [
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other'
];
header("HTTP/1.1 {$code} {$codes[$code]}");
header("Location: {$url}");
die();
}
}
// End of BaseController.php

View File

@ -1,4 +1,7 @@
<?php
/**
* Base DB model
*/
/**
* Base model for database interaction

View File

@ -1,4 +1,7 @@
<?php
/**
* Base for base models
*/
/**
* Common base for all Models
@ -35,6 +38,12 @@ class BaseModel {
$path = current($path_parts);
$ext_parts = explode('.', $path);
$ext = end($ext_parts);
// Workaround for some broken extensions
if ($ext == "jjpg") $ext = "jpg";
// Failsafe for weird urls
if (strlen($ext) > 3) return $api_path;
$cached_image = "{$series_slug}.{$ext}";
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";

View File

@ -1,4 +1,7 @@
<?php
/**
* Routing logic
*/
use Aura\Router\RouterFactory;
@ -90,11 +93,23 @@ class Router {
$controller_name = $route->params['controller'];
$action_method = $route->params['action'];
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
if ( ! empty($route->tokens))
{
foreach($route->tokens as $key => $v)
{
if (array_key_exists($key, $route->params))
{
$params[$key] = $route->params[$key];
}
}
}
}
$controller = new $controller_name();
// Run the appropriate controller method
$defaultHandler->addDataTable('controller_args', $params);
call_user_func_array([$controller, $action_method], $params);
}
@ -123,7 +138,20 @@ class Router {
{
$path = $route['path'];
unset($route['path']);
$this->router->add($name, $path)->addValues($route);
if ( ! array_key_exists('tokens', $route))
{
$this->router->add($name, $path)->addValues($route);
}
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
$this->router->add($name, $path)
->addValues($route)
->addTokens($tokens);
}
}
}
}

View File

@ -17,25 +17,50 @@ function is_selected($a, $b)
}
/**
* Generate full url path from the route path based on config
* Inverse of selected helper function
*
* @param string $path - The route path
* @param [string] $host - The controller (anime or manga), defaults to anime
* @param string $a - First item to compare
* @param string $b - Second item to compare
* @return string
*/
function full_url($path, $type="anime")
function is_not_selected($a, $b)
{
return ($a !== $b) ? 'selected' : '';
}
/**
* Generate full url path from the route path based on config
*
* @param string $path - (optional) The route path
* @param string $type - (optional) The controller (anime or manga), defaults to anime
* @return string
*/
function full_url($path="", $type="anime")
{
global $config;
$config_path = $config->{"{$type}_path"};
$config_host = $config->{"{$type}_host"};
$config_default_route = $config->{"default_{$type}_path"};
// Remove beginning/trailing slashes
$config_path = trim($config_path, '/');
$path = trim($path, '/');
// Remove any optional parameters from the route
$path = preg_replace('`{/.*?}`i', '', $path);
// Set the default view
if ($path === '')
{
$path .= trim($config_default_route, '/');
if ($config->default_to_list_view) $path .= '/list';
}
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set an leading folder
if ($config_path !== '')
{
$path = "{$config_path}/{$path}";
@ -44,4 +69,16 @@ function full_url($path, $type="anime")
return "//{$host}/{$path}";
}
/**
* Get the last segment of the current url
*
* @return string
*/
function last_segment()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', $path);
return end($segments);
}
// End of functions.php

View File

@ -3,6 +3,10 @@ return (object)[
// Username for feeds
'hummingbird_username' => 'timw4mail',
// Included config files
'routes' => require _dir(CONF_DIR, 'routes.php'),
'database' => require _dir(CONF_DIR, 'database.php'),
// ----------------------------------------------------------------------------
// Routing
//
@ -15,11 +19,14 @@ return (object)[
'anime_path' => '',
'manga_path' => '',
// Default pages for anime/manga
'default_anime_path' => '/watching',
'default_manga_path' => '/all',
// Default to list view?
'default_to_list_view' => FALSE,
// Cache paths
'data_cache_path' => _dir(APP_DIR, 'cache'),
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
// Included config files
'routes' => require _dir(CONF_DIR, 'routes.php'),
'database' => require _dir(CONF_DIR, 'database.php'),
];

View File

@ -2,120 +2,178 @@
return [
'anime' => [
'index' => [
'path' => '/',
'controller' => 'AnimeController',
'action' => 'redirect',
'params' => [
'url' => '', // Determined by config
'code' => '301'
]
],
'all' => [
'path' => '/all',
'path' => '/all{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'all',
'title' => WHOSE . " Anime List &middot All"
'title' => WHOSE . " Anime List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'index' => [
'path' => '/',
'watching' => [
'path' => '/watching{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot Watching"
'title' => WHOSE . " Anime List &middot; Watching"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_watch' => [
'path' => '/plan_to_watch',
'path' => '/plan_to_watch{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'plan-to-watch',
'title' => WHOSE . " Anime List &middot Plan to Watch"
'title' => WHOSE . " Anime List &middot; Plan to Watch"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold',
'path' => '/on_hold{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'on-hold',
'title' => WHOSE . " Anime List &middot On Hold"
'title' => WHOSE . " Anime List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped',
'path' => '/dropped{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'dropped',
'title' => WHOSE . " Anime List &middot Dropped"
'title' => WHOSE . " Anime List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed',
'path' => '/completed{/view}',
'controller' => 'AnimeController',
'action' => 'anime_list',
'params' => [
'type' => 'completed',
'title' => WHOSE . " Anime List &middot Completed"
'title' => WHOSE . " Anime List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'collection' => [
'path' => '/collection',
'path' => '/collection{/view}',
'controller' => 'AnimeController',
'action' => 'collection',
'params' => []
'params' => [],
'tokens' => [
'view' => '[a-z_]+'
]
]
],
'manga' => [
'index' => [
'path' => '/',
'controller' => 'MangaController',
'action' => 'redirect',
'params' => [
'url' => '', // Determined by config
'code' => '301',
'type' => 'manga'
]
],
'all' => [
'path' => '/all',
'path' => '/all{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'all',
'title' => WHOSE . " Manga List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'index' => [
'path' => '/',
'reading' => [
'path' => '/reading{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'Reading',
'title' => WHOSE . " Manga List &middot; Reading"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_read' => [
'path' => '/plan_to_read',
'path' => '/plan_to_read{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold',
'path' => '/on_hold{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'On Hold',
'title' => WHOSE . " Manga List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped',
'path' => '/dropped{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'Dropped',
'title' => WHOSE . " Manga List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed',
'path' => '/completed{/view}',
'controller' => 'MangaController',
'action' => 'manga_list',
'params' => [
'type' => 'Completed',
'title' => WHOSE . " Manga List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
]

View File

@ -1,4 +1,7 @@
<?php
/**
* Anime Controller
*/
/**
* Controller for Anime-related pages
@ -17,18 +20,24 @@ class AnimeController extends BaseController {
*/
private $collection_model;
/**
* Data to ve sent to all routes in this controller
* @var array $base_data
*/
private $base_data;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Watching' => '/',
'Plan to Watch' => '/plan_to_watch',
'On Hold' => '/on_hold',
'Dropped' => '/dropped',
'Completed' => '/completed',
'Collection' => '/collection',
'All' => '/all'
'Watching' => '/watching{/view}',
'Plan to Watch' => '/plan_to_watch{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'Collection' => '/collection{/view}',
'All' => '/all{/view}'
];
/**
@ -39,6 +48,12 @@ class AnimeController extends BaseController {
parent::__construct();
$this->model = new AnimeModel();
$this->collection_model = new AnimeCollectionModel();
$this->base_data = [
'url_type' => 'anime',
'other_type' => 'manga',
'nav_routes' => $this->nav_routes,
];
}
/**
@ -48,17 +63,21 @@ class AnimeController extends BaseController {
* @param string $title - The title of the page
* @return void
*/
public function anime_list($type, $title)
public function anime_list($type, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($type != 'all')
? $this->model->get_list($type)
: $this->model->get_all_lists();
$this->outputHTML('anime/list', [
$this->outputHTML('anime/' . $view_map[$view], array_merge($this->base_data, [
'title' => $title,
'nav_routes' => $this->nav_routes,
'sections' => $data
]);
]));
}
/**
@ -66,15 +85,19 @@ class AnimeController extends BaseController {
*
* @return void
*/
public function collection()
public function collection($view)
{
$view_map = [
'' => 'collection',
'list' => 'collection_list'
];
$data = $this->collection_model->get_collection();
$this->outputHTML('anime/collection', [
$this->outputHTML('anime/' . $view_map[$view], array_merge($this->base_data, [
'title' => WHOSE . " Anime Collection",
'nav_routes' => $this->nav_routes,
'sections' => $data
]);
]));
}
}
// End of AnimeController.php

View File

@ -1,56 +1,67 @@
<?php
/**
* Controller for manga list
*/
class MangaController extends BaseController {
/**
* The manga model
* @var object $model
*/
private $model;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Reading' => '/',
'Plan to Read' => '/plan_to_read',
'On Hold' => '/on_hold',
'Dropped' => '/dropped',
'Completed' => '/completed',
'All' => '/all'
];
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->model = new MangaModel();
}
/**
* Get a section of the manga list
*
* @param string $status
* @param string $title
* @return void
*/
public function manga_list($status, $title)
{
$data = ($status !== 'all')
? [$status => $this->model->get_list($status)]
: $this->model->get_all_lists();
$this->outputHTML('manga/list', [
'title' => $title,
'nav_routes' => $this->nav_routes,
'sections' => $data
]);
}
}
<?php
/**
* Manga Controller
*/
/**
* Controller for manga list
*/
class MangaController extends BaseController {
/**
* The manga model
* @var object $model
*/
private $model;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Reading' => '/reading{/view}',
'Plan to Read' => '/plan_to_read{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'All' => '/all{/view}'
];
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->model = new MangaModel();
}
/**
* Get a section of the manga list
*
* @param string $status
* @param string $title
* @param string $view
* @return void
*/
public function manga_list($status, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($status !== 'all')
? [$status => $this->model->get_list($status)]
: $this->model->get_all_lists();
$this->outputHTML('manga/' . $view_map[$view], [
'url_type' => 'manga',
'other_type' => 'anime',
'title' => $title,
'nav_routes' => $this->nav_routes,
'sections' => $data
]);
}
}
// End of MangaController.php

View File

@ -1,4 +1,7 @@
<?php
/**
* Anime Collection DB Model
*/
/**
* Model for getting anime collection data

View File

@ -1,4 +1,7 @@
<?php
/**
* Anime API Model
*/
/**
* Model for handling requests dealing with the anime list
@ -203,7 +206,7 @@ class AnimeModel extends BaseApiModel {
/**
* Sort the list by title
*
* @param array &$array
* @param array $array
* @return void
*/
private function sort_by_name(&$array)

View File

@ -1,164 +1,168 @@
<?php
/**
* Model for handling requests dealing with the manga list
*/
class MangaModel extends BaseApiModel {
/**
* @var string $base_url - The base url for api requests
*/
protected $base_url = "https://hummingbird.me";
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$data = $this->_get_list();
foreach ($data as $key => &$val)
{
$this->sort_by_name($val);
}
return $data;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$data = $this->_get_list($status);
$this->sort_by_name($data);
return $data;
}
/**
* Massage the list of manga entries into something more usable
*
* @param string $status
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
$config = [
'query' => [
'user_id' => $this->config->hummingbird_username
],
'allow_redirects' => false
];
$response = $this->client->get($this->_url('/manga_library_entries'), $config);
$defaultHandler->addDataTable('response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
// Reorganize data to be more usable
$raw_data = $response->json();
// Cache data in case of downtime
file_put_contents($cache_file, json_encode($raw_data));
}
$data = [
'Reading' => [],
'Plan to Read' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$manga_data = [];
// Massage the two lists into one
foreach($raw_data['manga'] as $manga)
{
$manga_data[$manga['id']] = $manga;
}
// Filter data by status
foreach($raw_data['manga_library_entries'] as &$entry)
{
$entry['manga'] = $manga_data[$entry['manga_id']];
// Cache poster images
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga_id'], 'manga');
switch($entry['status'])
{
case "Plan to Read":
$data['Plan to Read'][] = $entry;
break;
case "Dropped":
$data['Dropped'][] = $entry;
break;
case "On Hold":
$data['On Hold'][] = $entry;
break;
case "Currently Reading":
$data['Reading'][] = $entry;
break;
case "Completed":
default:
$data['Completed'][] = $entry;
break;
}
}
return (array_key_exists($status, $data)) ? $data[$status] : $data;
}
/**
* Sort the manga entries by their title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['manga']['romaji_title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
<?php
/**
* Manga API Model
*/
/**
* Model for handling requests dealing with the manga list
*/
class MangaModel extends BaseApiModel {
/**
* @var string $base_url - The base url for api requests
*/
protected $base_url = "https://hummingbird.me";
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$data = $this->_get_list();
foreach ($data as $key => &$val)
{
$this->sort_by_name($val);
}
return $data;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$data = $this->_get_list($status);
$this->sort_by_name($data);
return $data;
}
/**
* Massage the list of manga entries into something more usable
*
* @param string $status
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
$config = [
'query' => [
'user_id' => $this->config->hummingbird_username
],
'allow_redirects' => false
];
$response = $this->client->get($this->_url('/manga_library_entries'), $config);
$defaultHandler->addDataTable('response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
// Reorganize data to be more usable
$raw_data = $response->json();
// Cache data in case of downtime
file_put_contents($cache_file, json_encode($raw_data));
}
$data = [
'Reading' => [],
'Plan to Read' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$manga_data = [];
// Massage the two lists into one
foreach($raw_data['manga'] as $manga)
{
$manga_data[$manga['id']] = $manga;
}
// Filter data by status
foreach($raw_data['manga_library_entries'] as &$entry)
{
$entry['manga'] = $manga_data[$entry['manga_id']];
// Cache poster images
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga_id'], 'manga');
switch($entry['status'])
{
case "Plan to Read":
$data['Plan to Read'][] = $entry;
break;
case "Dropped":
$data['Dropped'][] = $entry;
break;
case "On Hold":
$data['On Hold'][] = $entry;
break;
case "Currently Reading":
$data['Reading'][] = $entry;
break;
case "Completed":
default:
$data['Completed'][] = $entry;
break;
}
}
return (array_key_exists($status, $data)) ? $data[$status] : $data;
}
/**
* Sort the manga entries by their title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['manga']['romaji_title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
// End of MangaModel.php

View File

@ -1,32 +1,27 @@
<body class="anime collection">
<h1><?= WHOSE ?> Anime Collection [<a href="<?= full_url('', 'manga') ?>">Manga List</a>]</h1>
<?php include 'nav.php' ?>
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $item['cover_image'] ?>" />
<div class="name">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
</div>
<div class="medium_metadata">
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age_rating"><?= $item['age_rating'] ?></div>
</div>
</article>
</a>
<?php endforeach ?>
</section>
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $item['cover_image'] ?>" />
<div class="name">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
</div>
<div class="medium_metadata">
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age_rating"><?= $item['age_rating'] ?></div>
</div>
</article>
</a>
<?php endforeach ?>
</section>
<?php endforeach ?>
</main>
</body>
</html>
</section>
<?php endforeach ?>
</main>

View File

@ -0,0 +1,43 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr>
<td class="align_left">
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['alternate_title'] ?></td>
<td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<td class="align_left"><?= $item['notes'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<br />
<?php endforeach ?>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

29
app/views/anime/cover.php Normal file
View File

@ -0,0 +1,29 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="<?= $item['anime']['url'] ?>">
<article class="media" id="a-<?= $item['anime']['id'] ?>">
<img class="round_all" src="<?= $item['anime']['cover_image'] ?>" />
<div class="round_all name">
<?= $item['anime']['title'] ?>
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="round_top airing_status"><?= $item['anime']['status'] ?></div>
<div class="user_rating"><?= (int)($item['rating']['value'] * 2) ?> / 10</div>
<div class="round_bottom completion">Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></div>
</div>
<div class="medium_metadata">
<div class="round_top media_type"><?= $item['anime']['show_type'] ?></div>
<div class="round_bottom age_rating"><?= $item['anime']['age_rating'] ?></div>
</div>
</article>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>

View File

@ -1,34 +1,42 @@
<body class="anime list">
<h1><?= WHOSE ?> Anime List [<a href="<?= full_url('', 'manga') ?>">Manga List</a>]</h1>
<?php include 'nav.php' ?>
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<main>
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Airing Status</th>
<th>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="a-<?= $item['anime']['id'] ?>">
<td class="align_left">
<a href="<?= $item['anime']['url'] ?>">
<article class="media" id="a-<?= $item['anime']['id'] ?>">
<img class="round_all" src="<?= $item['anime']['cover_image'] ?>" />
<div class="round_all name">
<?= $item['anime']['title'] ?>
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="round_top airing_status"><?= $item['anime']['status'] ?></div>
<div class="user_rating"><?= (int)($item['rating']['value'] * 2) ?> / 10</div>
<div class="round_bottom completion">Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></div>
</div>
<div class="medium_metadata">
<div class="round_top media_type"><?= $item['anime']['show_type'] ?></div>
<div class="round_bottom age_rating"><?= $item['anime']['age_rating'] ?></div>
</div>
</article>
<?= $item['anime']['title'] ?>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>
</body>
</html>
</td>
<td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
<td class="align_left"><?= $item['anime']['status'] ?></td>
<td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
<td><?= $item['anime']['show_type'] ?></td>
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
<td><?= $item['anime']['age_rating'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endforeach ?>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

View File

@ -1,7 +0,0 @@
<nav>
<ul>
<?php foreach($nav_routes as $title => $path): ?>
<li class="<?= is_selected($path, $route_path) ?>"><a href="<?= full_url($path) ?>"><?= $title ?></a></li>
<?php endforeach ?>
</ul>
</nav>

2
app/views/footer.php Normal file
View File

@ -0,0 +1,2 @@
</body>
</html>

View File

@ -1,7 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= $title ?></title>
<link rel="stylesheet" href="/public/css/marx.css" />
<link rel="stylesheet" href="/public/css/base.css" />
</head>
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= $title ?></title>
<link rel="stylesheet" href="/public/css/marx.css" />
<link rel="stylesheet" href="/public/css/base.css" />
</head>
<body class="<?= $url_type ?> list">
<h1><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</h1>
<nav>
<ul>
<?php foreach($nav_routes as $title => $nav_path): ?>
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
<?php endforeach ?>
</ul>
</nav>
<br />
<nav>
<ul>
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li>
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
</ul>
</nav>
<br />

30
app/views/manga/cover.php Normal file
View File

@ -0,0 +1,30 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
<article class="media" id="manga-<?= $item['manga']['id'] ?>">
<img src="<?= $item['manga']['poster_image'] ?>" />
<div class="name">
<?= $item['manga']['romaji_title'] ?>
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="user_rating"><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
<div class="completion">
Chapters: <?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?><?php /*<br />
Volumes: <?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?>*/ ?>
</div>
</div>
<?php /*<div class="medium_metadata">
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
</div> */ ?>
</article>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>

View File

@ -1,35 +1,40 @@
<body class="manga list">
<h1><?= WHOSE ?> Manga List [<a href="<?= full_url("", "anime") ?>">Anime List</a>]</h1>
<?php include 'nav.php' ?>
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
<article class="media" id="manga-<?= $item['manga']['id'] ?>">
<img src="<?= $item['manga']['poster_image'] ?>" />
<div class="name">
<?= $item['manga']['romaji_title'] ?>
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
</div>
<div class="media_metadata">
<div class="user_rating"><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
<div class="completion">
Chapters: <?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?><?php /*<br />
Volumes: <?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?>*/ ?>
</div>
</div>
<?php /*<div class="medium_metadata">
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
</div> */ ?>
</article>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>
</body>
</html>
<main>
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Rating</th>
<th>Chapters</th>
<!-- <th>Volumes</th> -->
<th>Type</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="manga-<?= $item['manga']['id'] ?>">
<td class="align_left">
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
<?= $item['manga']['romaji_title'] ?>
</a>
</td>
<td class="align_left"><?= (array_key_exists('english_title', $item['manga'])) ? $item['manga']['english_title'] : "" ?></td>
<td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
<td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
<!-- <td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td> -->
<td><?= $item['manga']['manga_type'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endforeach ?>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

View File

@ -1,7 +0,0 @@
<nav>
<ul>
<?php foreach($nav_routes as $title => $path): ?>
<li class="<?= is_selected($path, $route_path) ?>"><a href="<?= full_url($path, 'manga') ?>"><?= $title ?></a></li>
<?php endforeach ?>
</ul>
</nav>

View File

@ -1,4 +1,7 @@
<?php
/**
* Here begins everything!
*/
/**
* Joins paths together. Variadic to take an
@ -8,13 +11,15 @@
*/
function _dir() { return implode(DIRECTORY_SEPARATOR, func_get_args()); }
// Base paths
define('ROOT_DIR', __DIR__);
define('APP_DIR', _dir(ROOT_DIR, 'app'));
define('CONF_DIR', _dir(APP_DIR, 'config'));
define('BASE_DIR', _dir(APP_DIR, 'base'));
// Who's list is it?
/**
* Well, whose list is it?
*/
define('WHOSE', "Tim's");
// Load config and global functions

View File

@ -2,6 +2,23 @@ body {
margin: 0.5em;
}
table {
width:85%;
margin: 0 auto;
}
tbody > tr:nth-child(odd) {
background: #ddd;
}
.align_left {
text-align:left;
}
.align_right {
text-align:right;
}
.round_all {
border-radius:0.5em;
}

View File

@ -0,0 +1,122 @@
/*
* Metadata - jQuery plugin for parsing metadata from elements
*
* Copyright (c) 2006 John Resig, Yehuda Katz, J<EFBFBD>örn Zaefferer, Paul McLanahan
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id$
*
*/
/**
* Sets the type of metadata to use. Metadata is encoded in JSON, and each property
* in the JSON will become a property of the element itself.
*
* There are three supported types of metadata storage:
*
* attr: Inside an attribute. The name parameter indicates *which* attribute.
*
* class: Inside the class attribute, wrapped in curly braces: { }
*
* elem: Inside a child element (e.g. a script tag). The
* name parameter indicates *which* element.
*
* The metadata for an element is loaded the first time the element is accessed via jQuery.
*
* As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
* matched by expr, then redefine the metadata type and run another $(expr) for other elements.
*
* @name $.metadata.setType
*
* @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("class")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from the class attribute
*
* @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("attr", "data")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a "data" attribute
*
* @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
* @before $.metadata.setType("elem", "script")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a nested script element
*
* @param String type The encoding type
* @param String name The name of the attribute to be used to get metadata (optional)
* @cat Plugins/Metadata
* @descr Sets the type of encoding to be used when loading metadata for the first time
* @type undefined
* @see metadata()
*/
(function($) {
$.extend({
metadata : {
defaults : {
type: 'class',
name: 'metadata',
cre: /({.*})/,
single: 'metadata'
},
setType: function( type, name ){
this.defaults.type = type;
this.defaults.name = name;
},
get: function( elem, opts ){
var settings = $.extend({},this.defaults,opts);
// check for empty string in single property
if ( !settings.single.length ) settings.single = 'metadata';
var data = $.data(elem, settings.single);
// returned cached data if it already exists
if ( data ) return data;
data = "{}";
if ( settings.type == "class" ) {
var m = settings.cre.exec( elem.className );
if ( m )
data = m[1];
} else if ( settings.type == "elem" ) {
if( !elem.getElementsByTagName )
return undefined;
var e = elem.getElementsByTagName(settings.name);
if ( e.length )
data = $.trim(e[0].innerHTML);
} else if ( elem.getAttribute != undefined ) {
var attr = elem.getAttribute( settings.name );
if ( attr )
data = attr;
}
if ( data.indexOf( '{' ) <0 )
data = "{" + data + "}";
data = eval("(" + data + ")");
$.data( elem, settings.single, data );
return data;
}
}
});
/**
* Returns the metadata object for the first member of the jQuery object.
*
* @name metadata
* @descr Returns element's metadata object
* @param Object opts An object contianing settings to override the defaults
* @type jQuery
* @cat Plugins/Metadata
*/
$.fn.metadata = function( opts ){
return $.metadata.get( this[0], opts );
};
})(jQuery);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long