From 77d2c29e38e979d075e8292e3175a631baf1d54a Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 22 May 2015 12:36:26 -0400 Subject: [PATCH] First commit --- app/base/BaseController.php | 77 +++++ app/base/BaseModel.php | 39 +++ app/base/Router.php | 88 ++++++ app/base/autoloader.php | 17 ++ app/config/routes.php | 42 +++ app/controllers/AnimeController.php | 41 +++ app/models/AnimeModel.php | 134 +++++++++ app/views/404.php | 8 + app/views/anime_list.php | 39 +++ app/views/footer.php | 0 app/views/header.php | 7 + composer.json | 7 + index.php | 38 +++ public/css/base.css | 66 +++++ public/css/marx.css | 436 ++++++++++++++++++++++++++++ 15 files changed, 1039 insertions(+) create mode 100644 app/base/BaseController.php create mode 100644 app/base/BaseModel.php create mode 100644 app/base/Router.php create mode 100644 app/base/autoloader.php create mode 100644 app/config/routes.php create mode 100644 app/controllers/AnimeController.php create mode 100644 app/models/AnimeModel.php create mode 100644 app/views/404.php create mode 100644 app/views/anime_list.php create mode 100644 app/views/footer.php create mode 100644 app/views/header.php create mode 100644 composer.json create mode 100644 index.php create mode 100644 public/css/base.css create mode 100644 public/css/marx.css diff --git a/app/base/BaseController.php b/app/base/BaseController.php new file mode 100644 index 00000000..0c9d2126 --- /dev/null +++ b/app/base/BaseController.php @@ -0,0 +1,77 @@ +get_route(); + $data['route_path'] = ($route) ? $router->get_route()->path : ""; + + $path = realpath(__DIR__ . "/../views/{$template}.php"); + + if ( ! is_file($path)) + { + throw new Exception("Invalid template : {$path}"); + } + + ob_start(); + extract($data); + include $path; + $buffer = ob_get_contents(); + ob_end_clean(); + + header("Content-type: text/html;charset=utf-8"); + echo $buffer; + die(); + } + + /** + * Output json with the proper content type + * + * @param mixed data + * @return void + */ + public function outputJSON($data) + { + if ( ! is_string($data)) + { + $data = json_encode($data); + } + + header("Content-type: application/json"); + echo $data; + } +} \ No newline at end of file diff --git a/app/base/BaseModel.php b/app/base/BaseModel.php new file mode 100644 index 00000000..1b218be3 --- /dev/null +++ b/app/base/BaseModel.php @@ -0,0 +1,39 @@ +cookieJar = new CookieJar(); + $this->client = new Client([ + 'base_url' => $this->base_url, + 'defaults' => [ + 'cookies' => $this->cookieJar, + 'headers' => [ + 'User-Agent' => $_SERVER['HTTP_USER_AGENT'], + 'Accept-Encoding' => 'application/json' + ], + 'timeout' => 5, + 'connect_timeout' => 5 + ] + ]); + } + + /** + * Get the full url path, since the base_url in Guzzle doesn't work correctly + * + * @param string $path + * @return string + */ + protected function _url($path) + { + return "{$this->base_url}{$path}"; + } +} +// End of BaseModel.php \ No newline at end of file diff --git a/app/base/Router.php b/app/base/Router.php new file mode 100644 index 00000000..9eda4276 --- /dev/null +++ b/app/base/Router.php @@ -0,0 +1,88 @@ +newInstance(); + $this->router = $router_factory->newInstance(); + + $this->_setup_routes(); + } + + /** + * Get the current route object, if one matches + * + * @return object + */ + public function get_route() + { + $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + $route = $this->router->match($path, $_SERVER); + + return $route; + } + + /** + * Handle the current route + * + * @param [object] $route + * @return void + */ + public function dispatch($route = NULL) + { + if (is_null($route)) + { + $route = $this->get_route(); + } + + if ( ! $route) + { + $controller_name = 'BaseController'; + $action_method = 'outputHTML'; + $params = [ + 'template' => '404', + 'data' => [ + 'title' => 'Page Not Found' + ] + ]; + } + else + { + $controller_name = $route->params['controller']; + $action_method = $route->params['action']; + $params = (isset($route->params['params'])) ? $route->params['params'] : []; + } + + $controller = new $controller_name(); + + // Run the appropriate controller method + call_user_func_array([$controller, $action_method], $params); + } + + private function _setup_routes() + { + $host = $_SERVER['HTTP_HOST']; + $controller_class = ($host == "anime.timshomepage.net") ? "AnimeController" : "MangaController"; + + $routes = require __DIR__ . '/../config/routes.php'; + + // Add the default route + $this->router->add('home', '/')->addValues([ + 'controller' => $controller_class, + 'action' => 'index' + ]); + + // Add routes by the configuration file + foreach($routes as $name => $route) + { + $path = $route['path']; + unset($route['path']); + $this->router->add($name, $path)->addValues($route); + } + } +} +// End of Router.php \ No newline at end of file diff --git a/app/base/autoloader.php b/app/base/autoloader.php new file mode 100644 index 00000000..7cc5dba0 --- /dev/null +++ b/app/base/autoloader.php @@ -0,0 +1,17 @@ + [ + 'path' => '/all', + 'controller' => 'AnimeController', + 'action' => 'all', + 'params' => [] + ], + 'anime_plan_to_watch' => [ + 'path' => '/plan_to_watch', + 'controller' => 'AnimeController', + 'action' => 'anime_list', + 'params' => [ + 'type' => 'plan-to-watch' + ] + ], + 'anime_on_hold' => [ + 'path' => '/on_hold', + 'controller' => 'AnimeController', + 'action' => 'anime_list', + 'params' => [ + 'type' => 'on-hold' + ] + ], + 'anime_dropped' => [ + 'path' => '/dropped', + 'controller' => 'AnimeController', + 'action' => 'anime_list', + 'params' => [ + 'type' => 'dropped' + ] + ], + 'anime_completed' => [ + 'path' => '/completed', + 'controller' => 'AnimeController', + 'action' => 'anime_list', + 'params' => [ + 'type' => 'completed' + ] + ] +]; \ No newline at end of file diff --git a/app/controllers/AnimeController.php b/app/controllers/AnimeController.php new file mode 100644 index 00000000..9da9304a --- /dev/null +++ b/app/controllers/AnimeController.php @@ -0,0 +1,41 @@ +model = new AnimeModel(); + } + + public function __destruct() + { + parent::__destruct(); + } + + public function index() + { + $this->anime_list('currently-watching'); + } + + public function all() + { + $data = $this->model->get_all_lists(); + $this->outputHTML('anime_list', [ + 'title' => "Tim's Anime List · All", + 'sections' => $data + ]); + } + + public function anime_list($type, $title="Tim's Anime List") + { + $data = $this->model->get_list($type); + $this->outputHTML('anime_list', [ + 'title' => $title, + 'sections' => $data + ]); + } +} +// End of AnimeController.php \ No newline at end of file diff --git a/app/models/AnimeModel.php b/app/models/AnimeModel.php new file mode 100644 index 00000000..5d50b4ef --- /dev/null +++ b/app/models/AnimeModel.php @@ -0,0 +1,134 @@ + [], + 'Completed' => [], + 'Plan to Watch' => [], + 'On Hold' => [], + 'Dropped' => [] + ]; + + $data = $this->_get_list(); + + foreach($data as $datum) + { + switch($datum['status']) + { + case "completed": + $output['Completed'][] = $datum; + break; + + case "plan-to-watch": + $output['Plan to Watch'][] = $datum; + break; + + case "dropped": + $output['Dropped'][] = $datum; + break; + + case "on-hold": + $output['On Hold'][] = $datum; + break; + + case "currently-watching": + $output['Watching'][] = $datum; + break; + } + } + + // Sort anime by name + foreach($output as &$status_list) + { + $this->sort_by_name($status_list); + } + + return $output; + } + + /** + * Get a category out of the full list + * + * @param string $type + * @return array + */ + public function get_list($type) + { + $map = [ + 'currently-watching' => 'Watching', + 'completed' => 'Completed', + 'plan-to-watch' => 'Plan to Watch', + 'on-hold' => 'On Hold', + 'dropped' => 'Dropped' + ]; + + $data = $this->_get_list($type); + $this->sort_by_name($data); + + $output = []; + $output[$map[$type]] = $data; + + return $output; + } + + private function _get_list($type="all") + { + global $defaultHandler; + + $config = [ + 'query' => [ + 'username' => 'timw4mail', + ], + 'allow_redirects' => false + ]; + + if ($type != "all") + { + $config['query']['status'] = $type; + } + + $response = $this->client->get($this->_url('/users/timw4mail/library'), $config); + + $defaultHandler->addDataTable('response', (array)$response); + + if ($response->getStatusCode() != 200) + { + throw new Exception($response->getEffectiveUrl()); + } + + return $response->json(); + } + + private function sort_by_name(&$array) + { + $sort = array(); + + foreach($array as $key => $item) + { + $sort[$key] = $item['anime']['title']; + } + + array_multisort($sort, SORT_ASC, $array); + } +} +// End of AnimeModel.php \ No newline at end of file diff --git a/app/views/404.php b/app/views/404.php new file mode 100644 index 00000000..b0b7359a --- /dev/null +++ b/app/views/404.php @@ -0,0 +1,8 @@ + + +
+

404

+

Page Not Found

+
+ + \ No newline at end of file diff --git a/app/views/anime_list.php b/app/views/anime_list.php new file mode 100644 index 00000000..0554a1b6 --- /dev/null +++ b/app/views/anime_list.php @@ -0,0 +1,39 @@ + + +

Tim's Anime List

+ +
+ $items): ?> +
+

+
+ + + +
+
+ +
+ + \ No newline at end of file diff --git a/app/views/footer.php b/app/views/footer.php new file mode 100644 index 00000000..e69de29b diff --git a/app/views/header.php b/app/views/header.php new file mode 100644 index 00000000..c98b9b19 --- /dev/null +++ b/app/views/header.php @@ -0,0 +1,7 @@ + + + + <?= $title ?> + + + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..5d069476 --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "guzzlehttp/guzzle": "5.3.*", + "filp/whoops": "1.1.*", + "aura/router": "2.2.*" + } +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 00000000..1820300d --- /dev/null +++ b/index.php @@ -0,0 +1,38 @@ +pushHandler($defaultHandler); + +// Set up json handler for ajax errors +$jsonHandler = new JsonResponseHandler(); +$jsonHandler->onlyForAjaxRequests(true); +$whoops->pushHandler($jsonHandler); + +$whoops->register(); + +// ----------------------------------------------------------------------------- +// Router +// ----------------------------------------------------------------------------- + +$router = new Router(); +$defaultHandler->addDataTable('route', (array)$router->get_route()); +$router->dispatch(); + +// End of index.php \ No newline at end of file diff --git a/public/css/base.css b/public/css/base.css new file mode 100644 index 00000000..20916438 --- /dev/null +++ b/public/css/base.css @@ -0,0 +1,66 @@ +.media-wrap { + text-align:center; + margin:0 auto; +} + +.media { + position:relative; + vertical-align:top; + display:inline-block; + text-align:center; + width:220px; + height:319px; + margin:0.25em; +} + .media > img { + border-radius:0.25em; + } + + .name, .media_type, .airing_status, .user_rating, .completion { + background: rgba(0, 0, 0, 0.45); + color: #fff; + padding:0.25em; + text-align:right; + } + + .media > .media_metadata { + position:absolute; + bottom:0; + right:0; + } + + .media > .media_metadata > .media_type { + border-top-left-radius: 0.25em; + border-top-right-radius: 0.25em; + } + + .media > .media_metadata > .completion { + border-bottom-left-radius: 0.25em; + border-bottom-right-radius: 0.25em; + } + + .media > .name { + border-radius:0.25em; + position:absolute; + top: 0; + } + + .media > .name:hover { + background:rgba(0,0,0,0.9); + } + + .media > .name > a { + text-align:justify; + background:none; + color:#fff; + text-shadow: 1px 2px 1px rgba(0, 0, 0, 0.85); + } + +.user_rating::before { + content: "Rating: "; +} + +.completion::before { + content: "Episodes: "; +} + diff --git a/public/css/marx.css b/public/css/marx.css new file mode 100644 index 00000000..baafdaf3 --- /dev/null +++ b/public/css/marx.css @@ -0,0 +1,436 @@ +:root { + box-sizing: border-box; + cursor: default; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif; + line-height: 1.4; + overflow-y: scroll; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-size-adjust: 100%; } + +audio:not([controls]) { + display: none; } + +details { + display: block; } + +input[type="number"] { + width: auto; } +input[type="search"] { + -webkit-appearance: textfield; } + input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +main { + display: block; } + +summary { + display: block; } + +pre { + overflow: auto; } + +progress { + display: inline-block; } + +small { + font-size: 75%; } + +big { + font-size: 125%; } + +template { + display: none; } + +textarea { + overflow: auto; + resize: vertical; } + +[hidden] { + display: none; } + +[unselectable] { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +*, ::before, ::after { + border-style: solid; + border-width: 0; + box-sizing: inherit; } + +* { + font-size: inherit; + line-height: inherit; + margin: 0; + padding: 0; } + +::before, ::after { + text-decoration: inherit; + vertical-align: inherit; } + +a { + text-decoration: none; } + +audio, canvas, iframe, img, svg, video { + vertical-align: middle; } + +button, input, select, textarea { + background-color: transparent; + border: .1rem solid #ccc; + color: inherit; + font-family: inherit; + font-style: inherit; + font-weight: inherit; + min-height: 1.4em; } + +code, kbd, pre, samp { + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace, monospace; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +::-moz-selection { + background-color: #b3d4fc; + text-shadow: none; } + +::selection { + background-color: #b3d4fc; + text-shadow: none; } + +button::-moz-focus-inner { + border: 0; } + +@media screen { + [hidden~="screen"] { + display: inherit; } + [hidden~="screen"]:not(:active):not(:focus):not(:target) { + clip: rect(0 0 0 0) !important; + position: absolute !important; } } + +body { + color: #444; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif; + font-size: 1.6rem; + font-style: normal; + font-weight: 400; } + +p { + margin: 0 0 1.6rem; } + +h1, h2, h3, h4, h5, h6 { + font-family: 'Lato', 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif; + margin: 2rem 0 1.6rem; } + +h1 { + border-bottom: .1rem solid rgba(0, 0, 0, 0.2); + font-size: 3.6rem; + font-style: normal; + font-weight: 500; } + +h2 { + font-size: 3rem; + font-style: normal; + font-weight: 500; } + +h3 { + font-size: 2.4rem; + font-style: normal; + font-weight: 500; + margin: 1.6rem 0 0.4rem; } + +h4 { + font-size: 1.8rem; + font-style: normal; + font-weight: 600; + margin: 1.6rem 0 0.4rem; } + +h5 { + font-size: 1.6rem; + font-style: normal; + font-weight: 600; + margin: 1.6rem 0 0.4rem; } + +h6 { + color: #777; + font-size: 1.4rem; + font-style: normal; + font-weight: 600; + margin: 1.6rem 0 0.4rem; } + +small { + color: #777; } + +pre { + background: #efefef; + color: #444; + display: block; + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; + font-size: 1.4rem; + margin: 1.6rem 0; + padding: 1.6rem; + word-break: break-all; + word-wrap: break-word; } + +code { + background: #efefef; + color: #444; + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; + font-size: 1.4rem; + word-break: break-all; + word-wrap: break-word; } + +a { + color: #1271db; + -webkit-transition: .25s ease; + transition: .25s ease; } + a:hover, a:focus { + text-decoration: none; } + +dl { + margin-bottom: 1.6rem; } + +dd { + margin-left: 4rem; } + +ul, ol { + margin-bottom: 0.8rem; + padding-left: 2rem; } + +blockquote { + border-left: .2rem solid #1271db; + font-family: Georgia, Times, 'Times New Roman', serif; + font-style: italic; + margin: 1.6rem 0; + padding-left: 1.6rem; } + +figcaption { + font-family: Georgia, Times, 'Times New Roman', serif; } + +html { + font-size: 62.5%; } + +body { + padding: 0; } + +main, header, footer, article, section, aside, details, summary { + display: block; + height: auto; + margin: 0 auto; + width: 100%; } + +main { + display: block; + margin: 0 auto; + padding: 0 1.6rem 1.6rem; } + +footer { + border-top: .1rem solid rgba(0, 0, 0, 0.2); + clear: both; + display: inline-block; + float: left; + max-width: 100%; + padding: 1rem 0; + text-align: center; } + +hr { + border-top: .1rem solid rgba(0, 0, 0, 0.2); + display: block; + margin-bottom: 1.6rem; + width: 100%; } + +img { + height: auto; + max-width: 100%; + vertical-align: baseline; } + +@media screen and (max-width: 40rem) { + article, section, aside { + clear: both; + display: block; + max-width: 100%; } + img { + margin-right: 1.6rem; } } + +input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select { + border: .1rem solid #ccc; + border-radius: 0; + display: inline-block; + padding: 0.8rem; + vertical-align: middle; } + +input:not([type]) { + -webkit-appearance: none; + background-clip: padding-box; + background-color: #fff; + border: .1rem solid #ccc; + border-radius: 0; + color: #444; + display: inline-block; + padding: 0.8rem; + text-align: left; } + +input[type="color"] { + padding: 0.8rem 1.6rem; } + +input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, select:focus, textarea:focus { + border-color: #b3d4fc; } + +input:not([type]):focus { + border-color: #b3d4fc; } + +input[type="radio"], input[type="checkbox"] { + vertical-align: middle; } + +input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { + outline: .1rem solid thin #444; } + +input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled], select[disabled], textarea[disabled] { + background-color: #efefef; + color: #777; + cursor: not-allowed; } + +input:not([type])[disabled] { + background-color: #efefef; + color: #777; + cursor: not-allowed; } + +input[readonly], select[readonly], textarea[readonly] { + background-color: #efefef; + border-color: #ccc; + color: #777; } + +input:focus:invalid, textarea:focus:invalid, select:focus:invalid { + border-color: #e9322d; + color: #b94a48; } + +input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { + outline-color: #ff4136; } + +select { + background-color: #fff; + border: .1rem solid #ccc; } + +select[multiple] { + height: auto; } + +label { + line-height: 2; } + +fieldset { + border: 0; + margin: 0; + padding: 0.8rem 0; } + +legend { + border-bottom: .1rem solid #ccc; + color: #444; + display: block; + margin-bottom: 0.8rem; + padding: 0.8rem 0; + width: 100%; } + +textarea { + border: .1rem solid #ccc; + border-radius: 0; + display: block; + margin-bottom: 0.8rem; + padding: 0.8rem; + vertical-align: middle; } + +input[type=submit], button { + background-color: transparent; + border: .2rem solid #444; + border-radius: 0; + color: #444; + cursor: pointer; + display: inline-block; + margin-bottom: 0.8rem; + margin-right: 0.4rem; + padding: 0.8rem 1.6rem; + text-align: center; + text-decoration: none; + text-transform: uppercase; + -webkit-transition: .25s ease; + transition: .25s ease; + -webkit-user-drag: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + vertical-align: baseline; } + input[type=submit] a, button a { + color: #444; } + +input[type=submit]::-moz-focus-inner, button::-moz-focus-inner { + padding: 0; } + +input[type=submit]:hover, button:hover { + background: #444; + border-color: #444; + color: #fff; } + input[type=submit]:hover a, button:hover a { + color: #fff; } + +input[type=submit]:active, button:active { + background: #6a6a6a; + border-color: #6a6a6a; + color: #fff; } + input[type=submit]:active a, button:active a { + color: #fff; } + +input[type=submit]:disabled, button:disabled { + box-shadow: none; + cursor: not-allowed; + opacity: .40; } + +nav ul { + list-style: none; + margin: 0; + padding: 0; + padding-top: 1.6rem; + text-align: center; } + nav ul li { + display: inline; } +nav a { + border-bottom: .2rem solid transparent; + color: #444; + padding: 0.8rem 1.6rem; + text-decoration: none; + -webkit-transition: .25s ease; + transition: .25s ease; } + nav a:hover, nav li.selected a { + border-color: rgba(0, 0, 0, 0.2); } + nav a:active { + border-color: rgba(0, 0, 0, 0.56); } + +table { + margin-bottom: 1.6rem; } + +caption { + padding: 0.8rem 0; } + +thead th { + background: #efefef; + color: #444; } + +tr { + background: #fff; + margin-bottom: 0.8rem; } + +th, td { + border: .1rem solid #ccc; + padding: 0.8rem 1.6rem; + text-align: center; + vertical-align: inherit; } + +tfoot tr { + background: none; } + +tfoot td { + color: #efefef; + font-size: 0.8rem; + font-style: italic; + padding: 1.6rem 0.4rem; } \ No newline at end of file