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 <?php
/**
* Base API Model
*/
use \GuzzleHttp\Client; use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar; use \GuzzleHttp\Cookie\CookieJar;

View File

@ -1,4 +1,7 @@
<?php <?php
/**
* Base Controller
*/
/** /**
* Base class for controllers, defines output methods * Base class for controllers, defines output methods
@ -24,26 +27,30 @@ class BaseController {
* Output a template to HTML, using the provided data * Output a template to HTML, using the provided data
* *
* @param string $template * @param string $template
* @param array/object $data * @param array|object $data
* @return void * @return void
*/ */
public function outputHTML($template, $data=[]) public function outputHTML($template, $data=[])
{ {
global $router; global $router, $defaultHandler;
$route = $router->get_route(); $route = $router->get_route();
$data['route_path'] = ($route) ? $router->get_route()->path : ""; $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}"); throw new Exception("Invalid template : {$path}");
die();
} }
ob_start(); ob_start();
extract($data); extract($data);
include _dir(APP_DIR, 'views', 'header.php'); include _dir(APP_DIR, 'views', 'header.php');
include $path; include $template_path;
include _dir(APP_DIR, 'views', 'footer.php');
$buffer = ob_get_contents(); $buffer = ob_get_contents();
ob_end_clean(); ob_end_clean();
@ -55,7 +62,7 @@ class BaseController {
/** /**
* Output json with the proper content type * Output json with the proper content type
* *
* @param mixed data * @param mixed $data
* @return void * @return void
*/ */
public function outputJSON($data) public function outputJSON($data)
@ -68,5 +75,27 @@ class BaseController {
header("Content-type: application/json"); header("Content-type: application/json");
echo $data; 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 // End of BaseController.php

View File

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

View File

@ -1,4 +1,7 @@
<?php <?php
/**
* Base for base models
*/
/** /**
* Common base for all Models * Common base for all Models
@ -35,6 +38,12 @@ class BaseModel {
$path = current($path_parts); $path = current($path_parts);
$ext_parts = explode('.', $path); $ext_parts = explode('.', $path);
$ext = end($ext_parts); $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_image = "{$series_slug}.{$ext}";
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}"; $cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";

View File

@ -1,4 +1,7 @@
<?php <?php
/**
* Routing logic
*/
use Aura\Router\RouterFactory; use Aura\Router\RouterFactory;
@ -90,11 +93,23 @@ class Router {
$controller_name = $route->params['controller']; $controller_name = $route->params['controller'];
$action_method = $route->params['action']; $action_method = $route->params['action'];
$params = (isset($route->params['params'])) ? $route->params['params'] : []; $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(); $controller = new $controller_name();
// Run the appropriate controller method // Run the appropriate controller method
$defaultHandler->addDataTable('controller_args', $params);
call_user_func_array([$controller, $action_method], $params); call_user_func_array([$controller, $action_method], $params);
} }
@ -123,7 +138,20 @@ class Router {
{ {
$path = $route['path']; $path = $route['path'];
unset($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 $a - First item to compare
* @param [string] $host - The controller (anime or manga), defaults to anime * @param string $b - Second item to compare
* @return string * @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; global $config;
$config_path = $config->{"{$type}_path"}; $config_path = $config->{"{$type}_path"};
$config_host = $config->{"{$type}_host"}; $config_host = $config->{"{$type}_host"};
$config_default_route = $config->{"default_{$type}_path"};
// Remove beginning/trailing slashes // Remove beginning/trailing slashes
$config_path = trim($config_path, '/'); $config_path = trim($config_path, '/');
$path = trim($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']; $host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set an leading folder
if ($config_path !== '') if ($config_path !== '')
{ {
$path = "{$config_path}/{$path}"; $path = "{$config_path}/{$path}";
@ -44,4 +69,16 @@ function full_url($path, $type="anime")
return "//{$host}/{$path}"; 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 // End of functions.php

View File

@ -3,6 +3,10 @@ return (object)[
// Username for feeds // Username for feeds
'hummingbird_username' => 'timw4mail', 'hummingbird_username' => 'timw4mail',
// Included config files
'routes' => require _dir(CONF_DIR, 'routes.php'),
'database' => require _dir(CONF_DIR, 'database.php'),
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Routing // Routing
// //
@ -15,11 +19,14 @@ return (object)[
'anime_path' => '', 'anime_path' => '',
'manga_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 // Cache paths
'data_cache_path' => _dir(APP_DIR, 'cache'), 'data_cache_path' => _dir(APP_DIR, 'cache'),
'img_cache_path' => _dir(ROOT_DIR, 'public/images'), '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 [ return [
'anime' => [ 'anime' => [
'index' => [
'path' => '/',
'controller' => 'AnimeController',
'action' => 'redirect',
'params' => [
'url' => '', // Determined by config
'code' => '301'
]
],
'all' => [ 'all' => [
'path' => '/all', 'path' => '/all{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'all', 'type' => 'all',
'title' => WHOSE . " Anime List &middot All" 'title' => WHOSE . " Anime List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'index' => [ 'watching' => [
'path' => '/', 'path' => '/watching{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'currently-watching', 'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot Watching" 'title' => WHOSE . " Anime List &middot; Watching"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'plan_to_watch' => [ 'plan_to_watch' => [
'path' => '/plan_to_watch', 'path' => '/plan_to_watch{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'plan-to-watch', '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' => [ 'on_hold' => [
'path' => '/on_hold', 'path' => '/on_hold{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'on-hold', 'type' => 'on-hold',
'title' => WHOSE . " Anime List &middot On Hold" 'title' => WHOSE . " Anime List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'dropped' => [ 'dropped' => [
'path' => '/dropped', 'path' => '/dropped{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'dropped', 'type' => 'dropped',
'title' => WHOSE . " Anime List &middot Dropped" 'title' => WHOSE . " Anime List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'completed' => [ 'completed' => [
'path' => '/completed', 'path' => '/completed{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'anime_list', 'action' => 'anime_list',
'params' => [ 'params' => [
'type' => 'completed', 'type' => 'completed',
'title' => WHOSE . " Anime List &middot Completed" 'title' => WHOSE . " Anime List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'collection' => [ 'collection' => [
'path' => '/collection', 'path' => '/collection{/view}',
'controller' => 'AnimeController', 'controller' => 'AnimeController',
'action' => 'collection', 'action' => 'collection',
'params' => [] 'params' => [],
'tokens' => [
'view' => '[a-z_]+'
]
] ]
], ],
'manga' => [ 'manga' => [
'index' => [
'path' => '/',
'controller' => 'MangaController',
'action' => 'redirect',
'params' => [
'url' => '', // Determined by config
'code' => '301',
'type' => 'manga'
]
],
'all' => [ 'all' => [
'path' => '/all', 'path' => '/all{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'all', 'type' => 'all',
'title' => WHOSE . " Manga List &middot; All" 'title' => WHOSE . " Manga List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'index' => [ 'reading' => [
'path' => '/', 'path' => '/reading{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'Reading', 'type' => 'Reading',
'title' => WHOSE . " Manga List &middot; Reading" 'title' => WHOSE . " Manga List &middot; Reading"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'plan_to_read' => [ 'plan_to_read' => [
'path' => '/plan_to_read', 'path' => '/plan_to_read{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'Plan to Read', 'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read" 'title' => WHOSE . " Manga List &middot; Plan to Read"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'on_hold' => [ 'on_hold' => [
'path' => '/on_hold', 'path' => '/on_hold{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'On Hold', 'type' => 'On Hold',
'title' => WHOSE . " Manga List &middot; On Hold" 'title' => WHOSE . " Manga List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'dropped' => [ 'dropped' => [
'path' => '/dropped', 'path' => '/dropped{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'Dropped', 'type' => 'Dropped',
'title' => WHOSE . " Manga List &middot; Dropped" 'title' => WHOSE . " Manga List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
'completed' => [ 'completed' => [
'path' => '/completed', 'path' => '/completed{/view}',
'controller' => 'MangaController', 'controller' => 'MangaController',
'action' => 'manga_list', 'action' => 'manga_list',
'params' => [ 'params' => [
'type' => 'Completed', 'type' => 'Completed',
'title' => WHOSE . " Manga List &middot; Completed" 'title' => WHOSE . " Manga List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
] ]
], ],
] ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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"> <main>
<h1><?= WHOSE ?> Anime List [<a href="<?= full_url('', 'manga') ?>">Manga List</a>]</h1> <?php foreach ($sections as $name => $items): ?>
<?php include 'nav.php' ?> <h2><?= $name ?></h2>
<main> <table>
<?php foreach ($sections as $name => $items): ?> <thead>
<section class="status"> <tr>
<h2><?= $name ?></h2> <th>Title</th>
<section class="media-wrap"> <th>Alternate Title</th>
<?php foreach($items as $item): ?> <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'] ?>"> <a href="<?= $item['anime']['url'] ?>">
<article class="media" id="a-<?= $item['anime']['id'] ?>"> <?= $item['anime']['title'] ?>
<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> </a>
<?php endforeach ?> </td>
</section> <td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
</section> <td class="align_left"><?= $item['anime']['status'] ?></td>
<?php endforeach ?> <td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
</main> <td><?= $item['anime']['show_type'] ?></td>
</body> <td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
</html> <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> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title><?= $title ?></title> <title><?= $title ?></title>
<link rel="stylesheet" href="/public/css/marx.css" /> <link rel="stylesheet" href="/public/css/marx.css" />
<link rel="stylesheet" href="/public/css/base.css" /> <link rel="stylesheet" href="/public/css/base.css" />
</head> </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"> <main>
<h1><?= WHOSE ?> Manga List [<a href="<?= full_url("", "anime") ?>">Anime List</a>]</h1> <?php foreach ($sections as $name => $items): ?>
<?php include 'nav.php' ?> <h2><?= $name ?></h2>
<main> <table>
<?php foreach ($sections as $name => $items): ?> <thead>
<section class="status"> <tr>
<h2><?= $name ?></h2> <th>Title</th>
<section class="media-wrap"> <th>Alternate Title</th>
<?php foreach($items as $item): ?> <th>Rating</th>
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>"> <th>Chapters</th>
<article class="media" id="manga-<?= $item['manga']['id'] ?>"> <!-- <th>Volumes</th> -->
<img src="<?= $item['manga']['poster_image'] ?>" /> <th>Type</th>
<div class="name"> </tr>
<?= $item['manga']['romaji_title'] ?> </thead>
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?> <tbody>
</div> <?php foreach($items as $item): ?>
<div class="media_metadata"> <tr id="manga-<?= $item['manga']['id'] ?>">
<div class="user_rating"><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div> <td class="align_left">
<div class="completion"> <a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
Chapters: <?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?><?php /*<br /> <?= $item['manga']['romaji_title'] ?>
Volumes: <?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?>*/ ?> </a>
</div> </td>
</div> <td class="align_left"><?= (array_key_exists('english_title', $item['manga'])) ? $item['manga']['english_title'] : "" ?></td>
<?php /*<div class="medium_metadata"> <td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
<div class="media_type"><?= $item['manga']['manga_type'] ?></div> <td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
</div> */ ?> <!-- <td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td> -->
</article> <td><?= $item['manga']['manga_type'] ?></td>
</a> </tr>
<?php endforeach ?> <?php endforeach ?>
</section> </tbody>
</section> </table>
<?php endforeach ?> <?php endforeach ?>
</main> </main>
</body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
</html> <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 <?php
/**
* Here begins everything!
*/
/** /**
* Joins paths together. Variadic to take an * Joins paths together. Variadic to take an
@ -8,13 +11,15 @@
*/ */
function _dir() { return implode(DIRECTORY_SEPARATOR, func_get_args()); } function _dir() { return implode(DIRECTORY_SEPARATOR, func_get_args()); }
// Base paths
define('ROOT_DIR', __DIR__); define('ROOT_DIR', __DIR__);
define('APP_DIR', _dir(ROOT_DIR, 'app')); define('APP_DIR', _dir(ROOT_DIR, 'app'));
define('CONF_DIR', _dir(APP_DIR, 'config')); define('CONF_DIR', _dir(APP_DIR, 'config'));
define('BASE_DIR', _dir(APP_DIR, 'base')); define('BASE_DIR', _dir(APP_DIR, 'base'));
// Who's list is it? /**
* Well, whose list is it?
*/
define('WHOSE', "Tim's"); define('WHOSE', "Tim's");
// Load config and global functions // Load config and global functions

View File

@ -2,6 +2,23 @@ body {
margin: 0.5em; 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 { .round_all {
border-radius:0.5em; 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