From 91fd82fc12e524f38ad7ca7c9e6f99e4111d267c Mon Sep 17 00:00:00 2001 From: Matt McNaney Date: Thu, 11 Apr 2019 13:59:03 -0400 Subject: [PATCH 01/91] Beginning of rewrite --- Module.php | 177 +- boost/boost.php | 2 +- boost/controlpanel.php | 2 +- boost/update.php | 14 + class/Controller/Admin/Carousel.php | 75 + class/Controller/Admin/Slide.php | 63 + class/Controller/AjaxLoginError.php | 28 + class/Controller/Controller.php | 117 + class/Controller/SubController.php | 182 + class/Exception/BadCommand.php | 32 + class/Exception/MissingInput.php | 17 + class/Exception/PrivilegeMissing.php | 26 + class/Exception/ResourceNotFound.php | 36 + class/Factory/BaseFactory.php | 43 + class/Factory/CarouselFactory.php | 73 + class/Factory/SlideFactory.php | 95 + class/Resource/AbstractResource.php | 56 + class/Resource/CarouselResource.php | 46 + class/Resource/SlideResource.php | 66 + class/Role/Admin.php | 24 + class/Role/Base.php | 55 + class/Role/User.php | 21 + class/View/AbstractView.php | 102 + class/View/CarouselView.php | 21 + class/View/SlideView.php | 18 + config/defines.dist.php | 20 + config/defines.php | 20 + exports.js | 8 + javascript/Carousel/Form.jsx | 149 + javascript/Carousel/index.jsx | 100 + javascript/Extends/Grid.jsx | 125 + javascript/Extends/Listing.jsx | 355 + javascript/Extends/grid.scss | 12 + javascript/Slide/Form.js | 102 + javascript/Slide/index.jsx | 123 + javascript/Slide/style.css | 3 + javascript/Slide/style.css.map | 9 + javascript/Slide/style.scss | 22 + javascript/dev/Carousel.js | 1237 + javascript/dev/Listing.js | 229 + javascript/dev/Slide.js | 1226 + javascript/dev/vendor.js | 37848 +++++++++++++++++++++++++ package-lock.json | 8727 ++++++ package.json | 51 + templates/slideLogic.php | 2 +- webpack.config.js | 86 + 46 files changed, 51783 insertions(+), 62 deletions(-) create mode 100644 class/Controller/Admin/Carousel.php create mode 100644 class/Controller/Admin/Slide.php create mode 100644 class/Controller/AjaxLoginError.php create mode 100644 class/Controller/Controller.php create mode 100644 class/Controller/SubController.php create mode 100644 class/Exception/BadCommand.php create mode 100644 class/Exception/MissingInput.php create mode 100644 class/Exception/PrivilegeMissing.php create mode 100644 class/Exception/ResourceNotFound.php create mode 100644 class/Factory/BaseFactory.php create mode 100644 class/Factory/CarouselFactory.php create mode 100644 class/Factory/SlideFactory.php create mode 100644 class/Resource/AbstractResource.php create mode 100644 class/Resource/CarouselResource.php create mode 100644 class/Resource/SlideResource.php create mode 100644 class/Role/Admin.php create mode 100644 class/Role/Base.php create mode 100644 class/Role/User.php create mode 100644 class/View/AbstractView.php create mode 100644 class/View/CarouselView.php create mode 100644 class/View/SlideView.php create mode 100644 config/defines.dist.php create mode 100644 config/defines.php create mode 100644 exports.js create mode 100644 javascript/Carousel/Form.jsx create mode 100644 javascript/Carousel/index.jsx create mode 100644 javascript/Extends/Grid.jsx create mode 100644 javascript/Extends/Listing.jsx create mode 100644 javascript/Extends/grid.scss create mode 100644 javascript/Slide/Form.js create mode 100644 javascript/Slide/index.jsx create mode 100644 javascript/Slide/style.css create mode 100644 javascript/Slide/style.css.map create mode 100644 javascript/Slide/style.scss create mode 100644 javascript/dev/Carousel.js create mode 100644 javascript/dev/Listing.js create mode 100644 javascript/dev/Slide.js create mode 100644 javascript/dev/vendor.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 webpack.config.js diff --git a/Module.php b/Module.php index 87b1958..7fbd95f 100644 --- a/Module.php +++ b/Module.php @@ -1,12 +1,25 @@ * @license http://opensource.org/licenses/lgpl-3.0.html */ + +namespace carousel; + +use Canopy\Request; +use Canopy\Response; +use Canopy\Server; +use Canopy\SettingDefaults; +use carousel\Controller\Controller; + +if (is_file(PHPWS_SOURCE_DIR . 'mod/carousel/config/defines.php')) { + require_once PHPWS_SOURCE_DIR . 'mod/carousel/config/defines.php'; +} else { + require_once PHPWS_SOURCE_DIR . 'mod/carousel/config/defines.dist.php'; +} + class Module extends \Canopy\Module implements \Canopy\SettingDefaults { @@ -15,77 +28,58 @@ public function __construct() parent::__construct(); $this->setTitle('carousel'); $this->setProperName('Carousel for Bootstrap themes'); + spl_autoload_register('\carousel\Module::autoloader', true, true); } - public function getController(\Canopy\Request $request) + public static function autoloader($class_name) { - $cmd = $request->shiftCommand(); - if ($cmd == 'admin' && \Current_User::allow('carousel')) { - $admin = new \carousel\Controller\Admin($this); - return $admin; - } - } + static $not_found = array(); - public function runTime(\Canopy\Request $request) - { - if (!$request->isVar('module')) { - $display = \carousel\SlideFactory::display(); - if (!empty($display)) { - \Layout::add($display, 'carousel', 'slides'); - } + if (strpos($class_name, 'carousel') !== 0) { + return; } - } - public function afterRun(\Canopy\Request $request, \Canopy\Response $response) - { - $key = \Canopy\Key::getCurrent(); - if ($key && !$key->isDummy()) { - $this->checkKey($key->id); + if (isset($not_found[$class_name])) { + return; } - } + $class_array = explode('\\', $class_name); + array_shift($class_array); + $class_dir = implode('/', $class_array); - private function checkKey($key_id) - { - \Layout::addJSHeader(''); - $db = \phpws2\Database::newDB(); - $t = $db->addTable('caro_keyed_slide'); - $t->addField('slide_id'); - $t->addFieldConditional('key_id', $key_id); - $result = $db->selectColumn(); - if (\Current_User::allow('carousel')) { - $this->miniAdmin($result, $key_id); - } - if (!empty($result)) { - \carousel\SlideFactory::showKeySlide($result); + $class_path = PHPWS_SOURCE_DIR . 'mod/conference/class/' . $class_dir . '.php'; + if (is_file($class_path)) { + require_once $class_path; + return true; + } else { + $not_found[] = $class_name; + return false; } } - private function miniAdmin($result, $key_id) + public function getController(Request $request) { - if (empty($result)) { - $db = \phpws2\Database::newDB(); - $t2 = $db->addTable('caro_slide'); - $t2->addOrderBy('title'); - $t2->addField('title'); - $t2->addField('id'); - $slides = $db->select(); - if (empty($slides)) { - return; + try { + $controller = new Controller($this, $request); + return $controller; + } catch (\conference\Exception\PrivilegeMissing $e) { + if ($request->isGet() && !$request->isAjax()) { + \Current_User::requireLogin(); + } else { + throw $e; } - - $opt[] = ''; - foreach ($slides as $s) { - $opt[] = ''; - } - - $select = ''; - } else { - $select = '' . 'Remove slide from page' . ''; } - \MiniAdmin::add('carousel', $select); + /* + catch (\Exception $e) { + if (CAROUSEL_SYSTEM_SETTINGS['friendlyErrors']) { + \phpws2\Error::log($e); + $controller = new Controller\FriendlyErrorController($this); + return $controller; + } else { + throw $e; + } + } + * + */ } public function getSettingDefaults() @@ -101,6 +95,71 @@ public function getSettingDefaults() return $s; } + /* + public function runTime(\Canopy\Request $request) + { + if (!$request->isVar('module')) { + $display = \carousel\SlideFactory::display(); + if (!empty($display)) { + \Layout::add($display, 'carousel', 'slides'); + } + } + } + + public function afterRun(\Canopy\Request $request, + \Canopy\Response $response) + { + $key = \Canopy\Key::getCurrent(); + if ($key && !$key->isDummy()) { + $this->checkKey($key->id); + } + } + + private function checkKey($key_id) + { + \Layout::addJSHeader(''); + $db = \phpws2\Database::newDB(); + $t = $db->addTable('caro_keyed_slide'); + $t->addField('slide_id'); + $t->addFieldConditional('key_id', $key_id); + $result = $db->selectColumn(); + if (\Current_User::allow('carousel')) { + $this->miniAdmin($result, $key_id); + } + if (!empty($result)) { + \carousel\SlideFactory::showKeySlide($result); + } + } + + private function miniAdmin($result, $key_id) + { + if (empty($result)) { + $db = \phpws2\Database::newDB(); + $t2 = $db->addTable('caro_slide'); + $t2->addOrderBy('title'); + $t2->addField('title'); + $t2->addField('id'); + $slides = $db->select(); + if (empty($slides)) { + return; + } + + $opt[] = ''; + foreach ($slides as $s) { + $opt[] = ''; + } + + $select = ''; + } else { + $select = '' . 'Remove slide from page' . ''; + } + \MiniAdmin::add('carousel', $select); + } + * + */ } ?> diff --git a/boost/boost.php b/boost/boost.php index c43eb48..e7c6301 100644 --- a/boost/boost.php +++ b/boost/boost.php @@ -4,7 +4,7 @@ * @author Matthew McNaney */ $proper_name = 'Carousel for Bootstrap'; -$version = '1.5.1'; +$version = '2.0.0'; $import_sql = false; $image_dir = true; ?> diff --git a/boost/controlpanel.php b/boost/controlpanel.php index f6f1f80..73ce919 100644 --- a/boost/controlpanel.php +++ b/boost/controlpanel.php @@ -5,7 +5,7 @@ */ $link[] = array('label' => 'Carousel', 'restricted' => TRUE, - 'url' => 'carousel/admin/', + 'url' => 'carousel/Admin/List', 'description' => 'Image carousel for Bootstrap themes', 'image' => 'carousel.gif', 'tab' => 'content' diff --git a/boost/update.php b/boost/update.php index d6db744..eac8730 100644 --- a/boost/update.php +++ b/boost/update.php @@ -20,6 +20,20 @@ function carousel_update(&$content, $version) -------------------- + Fixed keyed Carousel images not showing. '; + case version_compare($version, '2.0.0', '<'): + $db = \phpws2\Database::getDB(); + $tbl = $db->addTable('controlpanel_link'); + $tbl->addValue('url', 'carousel/Admin/List'); + $tbl->addFieldConditional('itemname', 'carousel'); + $db->update(); + $content[] = '
2.0.0
+--------------------
++ Rewrite
+
'; + $db->clearConditional(); + $db->clearTables(); + $carousel = new \carousel\Resource\CarouselResource; + $carousel->createTable($db); } // end of switch return true; diff --git a/class/Controller/Admin/Carousel.php b/class/Controller/Admin/Carousel.php new file mode 100644 index 0000000..9868f8d --- /dev/null +++ b/class/Controller/Admin/Carousel.php @@ -0,0 +1,75 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Controller\Admin; + +use Canopy\Request; +use carousel\Controller\SubController; +use carousel\Factory\CarouselFactory as Factory; +use carousel\View\CarouselView as View; + +class Carousel extends SubController +{ + + /** + * @var carousel\View\CarouselView + */ + protected $view; + + /** + * @var carousel\Factory\CarouselFactory + */ + protected $factory; + + protected function loadFactory() + { + $this->factory = new Factory; + } + + protected function loadView() + { + $this->view = new View; + } + + protected function listHtml(Request $request) + { + \Layout::hideDefault(); + return $this->view->scriptView('Carousel'); + } + + protected function listJson(Request $request) + { + return ['listing' => $this->factory->listing()]; + } + + protected function post(Request $request) + { + $carousel = $this->factory->post($request); + $this->factory->save($carousel); + return ['success' => true]; + } + + protected function put(Request $request) + { + $carousel = $this->factory->put($this->id, $request); + $this->factory->save($carousel); + return ['success' => true]; + } + + protected function delete(Request $request) + { + $carousel = $this->factory->load($this->id); + $this->factory->delete($carousel); + return ['success' => true]; + } + +} diff --git a/class/Controller/Admin/Slide.php b/class/Controller/Admin/Slide.php new file mode 100644 index 0000000..80e4c10 --- /dev/null +++ b/class/Controller/Admin/Slide.php @@ -0,0 +1,63 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Controller\Admin; + +use Canopy\Request; +use carousel\Controller\SubController; +use carousel\Factory\SlideFactory as Factory; +use carousel\View\SlideView as View; + +class Slide extends SubController +{ + + /** + * @var carousel\View\SlideView + */ + protected $view; + + /** + * @var carousel\Factory\SlideFactory + */ + protected $factory; + + protected function loadFactory() + { + $this->factory = new Factory; + } + + protected function loadView() + { + $this->view = new View; + } + + protected function listHtml(Request $request) + { + $carouselId = $request->pullGetInteger('carousel'); + return $this->view->scriptView('Slide', true, + ['carouselId' => $carouselId]); + } + + protected function listJson(Request $request) + { + $listing = $this->factory->listing(); + return ['listing' => $listing]; + } + + protected function uploadPost(Request $request) + { + $carouselId = $request->pullPostInteger('carouselId'); + $this->factory->upload($carouselId); + return ['success' => true]; + } + +} diff --git a/class/Controller/AjaxLoginError.php b/class/Controller/AjaxLoginError.php new file mode 100644 index 0000000..dfd0f59 --- /dev/null +++ b/class/Controller/AjaxLoginError.php @@ -0,0 +1,28 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Controller; + +use Canopy\Request; + +class AjaxLoginError extends \phpws2\Http\Controller +{ + + public function get(Request $request) + { + $json = ['success'=>false, 'message'=>'login expired']; + $view = new \phpws2\View\JsonView($json); + $response = new \Canopy\Response($view); + return $response; + } + +} diff --git a/class/Controller/Controller.php b/class/Controller/Controller.php new file mode 100644 index 0000000..1661cce --- /dev/null +++ b/class/Controller/Controller.php @@ -0,0 +1,117 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Controller; + +use Canopy\Request; +use carousel\Factory\CarouselFactory; +use carousel\View\CarouselView; + +//define('CONFERENCE_ROLES', ['Admin', 'User', 'Visitor']); + +class Controller extends \phpws2\Http\Controller +{ + + protected $role; + protected $subcontroller; + + public function __construct(\Canopy\Module $module, Request $request) + { + parent::__construct($module); + $this->loadRole(); + $this->loadSubController($request); + } + + private function loadRole() + { + $userId = \Current_User::getId(); + if (\Current_User::allow('carousel')) { + $this->role = new \carousel\Role\Admin($userId); + } else { + $this->role = new \carousel\Role\User; + } + } + + /** + * Loads controller based on Role and Resource. Default Controller is a + * User Conference list view. + * @param Request $request + * @throws \carousel\Exception\PrivilegeMissing + * @throws \carousel\Exception\VisitorLoginRequired + * @throws \carousel\Exception\BadCommand + */ + private function loadSubController(Request $request) + { + $roleController = filter_var($request->shiftCommand(), + FILTER_SANITIZE_STRING); + + if (empty($roleController)) { + throw new \carousel\Exception\BadCommand('Missing role controller'); + } + + if ($roleController === 'Admin' && !$this->role->isAdmin()) { + throw new \carousel\Exception\PrivilegeMissing; + } + + $subController = filter_var($request->shiftCommand(), + FILTER_SANITIZE_STRING); + + if (empty($subController)) { + throw new \carousel\Exception\BadCommand('Missing subcontroller'); + } + + $subControllerName = '\\carousel\\Controller\\' . $roleController . '\\' . $subController; + if (!class_exists($subControllerName)) { + throw new \carousel\Exception\BadCommand($subControllerName); + } + $this->subcontroller = new $subControllerName($this->role); + } + + public function execute(Request $request) + { + try { + return parent::execute($request); + } catch (\carousel\Exception\PrivilegeMissing $e) { + \Current_User::requireLogin(); + } + } + + public function post(Request $request) + { + return $this->subcontroller->changeResponse($request); + } + + public function patch(Request $request) + { + return $this->subcontroller->changeResponse($request); + } + + public function delete(Request $request) + { + return $this->subcontroller->changeResponse($request); + } + + public function put(Request $request) + { + return $this->subcontroller->changeResponse($request); + } + + public function get(Request $request) + { + if ($request->isAjax() || (bool) $request->pullGetBoolean('json', true)) { + return $this->subcontroller->getJson($request); + } else { + return $this->subcontroller->getHtml($request); + } + } + +} diff --git a/class/Controller/SubController.php b/class/Controller/SubController.php new file mode 100644 index 0000000..ade4128 --- /dev/null +++ b/class/Controller/SubController.php @@ -0,0 +1,182 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Controller; + +use carousel\Exception\BadCommand; +use carousel\Exception\PrivilegeMissing; +use phpws2\Database; +use Canopy\Request; + +abstract class SubController +{ + + protected $factory; + protected $view; + protected $role; + protected $id; + + abstract protected function loadFactory(); + + abstract protected function loadView(); + + public function __construct($role) + { + $this->role = $role; + $this->loadFactory(); + $this->loadView(); + } + + /** + * Returns the current get command + * Defaults to a "view" command if an id is set and + * "list" if otherwise. + * @param Request $request + * @return string + */ + protected function pullGetCommand(Request $request) + { + $command = $request->shiftCommand(); + if (is_numeric($command)) { + $this->id = $command; + + $subcommand = $request->shiftCommand(); + if (empty($subcommand)) { + $command = 'view'; + } else { + return $subcommand; + } + } else if (empty($command)) { + $command = 'list'; + } + return $command; + } + + /** + * Loads the EXPECTED id from the url into the object. + * If the id is not there, the command fails + */ + protected function loadRequestId(Request $request) + { + $id = $request->shiftCommand(); + if (!is_numeric($id)) { + throw new \carousel\Exception\ResourceNotFound($id); + } + $this->id = $id; + } + + public function getHtml(Request $request) + { + $command = $this->pullGetCommand($request); + + $method_name = $command . 'Html'; + if (!method_exists($this, $method_name)) { + if ($this->id && method_exists($this, 'viewHtml')) { + $method_name = 'viewHtml'; + } else { + throw new BadCommand($method_name); + } + } + + $content = $this->$method_name($request); + return $this->htmlResponse($content); + } + + public function getJson(Request $request) + { + $command = $this->pullGetCommand($request); + + if (empty($command)) { + throw new BadCommand; + } + + $method_name = $command . 'Json'; + + /** + * Unlike getHtml, a bad command is not excused + */ + if (!method_exists($this, $method_name)) { + throw new BadCommand($method_name); + } + + $json = $this->$method_name($request); + return $this->jsonResponse($json); + } + + public function htmlResponse($content) + { + $view = new \phpws2\View\HtmlView($content); + $response = new \Canopy\Response($view); + return $response; + } + + public function jsonResponse($json) + { + $view = new \phpws2\View\JsonView($json); + $response = new \Canopy\Response($view); + return $response; + } + + /** + * For delete, post, patch, and put commands + * @param Request $request + */ + public function changeResponse(Request $request) + { + $method = strtolower($request->getMethod()); + if ($method !== 'post') { + $this->loadRequestId($request); + } + + $getCommand = $request->shiftCommand(); + + if (empty($getCommand)) { + $restCommand = $method; + } else { + $restCommand = $getCommand . ucfirst($method); + } + + if (!method_exists($this, $restCommand)) { + $errorMessage = get_class($this) . ':' . $restCommand; + throw new BadCommand($errorMessage); + } + + $content = $this->$restCommand($request); + + if ($request->isAjax()) { + return $this->jsonResponse($content); + } else { + return $this->htmlResponse($content); + } + } + + public function getResponse($content, Request $request) + { + return $request->isAjax() ? $this->jsonResponse($content) : $this->htmlResponse($content); + } + + protected function put(Request $request) + { + throw new BadCommand(__FUNCTION__); + } + + protected function patch(Request $request) + { + throw new BadCommand(__FUNCTION__); + } + + protected function delete(Request $request) + { + throw new BadCommand(__FUNCTION__); + } + +} diff --git a/class/Exception/BadCommand.php b/class/Exception/BadCommand.php new file mode 100644 index 0000000..b3c1be8 --- /dev/null +++ b/class/Exception/BadCommand.php @@ -0,0 +1,32 @@ +. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +namespace carousel\Exception; + +class BadCommand extends \Exception +{ + public function __construct($command=null) { + if ($command) { + $this->message = 'Unknown command sent to controller: ' . $command; + } else { + $this->message = 'Empty command sent to controller'; + } + } +} diff --git a/class/Exception/MissingInput.php b/class/Exception/MissingInput.php new file mode 100644 index 0000000..bcccaef --- /dev/null +++ b/class/Exception/MissingInput.php @@ -0,0 +1,17 @@ + + */ + +namespace carousel\Exception; + +class MissingInput extends \Exception +{ + +} diff --git a/class/Exception/PrivilegeMissing.php b/class/Exception/PrivilegeMissing.php new file mode 100644 index 0000000..fd6349a --- /dev/null +++ b/class/Exception/PrivilegeMissing.php @@ -0,0 +1,26 @@ + + * @license https://opensource.org/licenses/MIT + */ +namespace carousel\Exception; + +class PrivilegeMissing extends \Exception +{ + protected $defaultMessage = 'You do not have permissions for this action'; + + public function __construct($className = null) + { + $message = $this->defaultMessage; + if ($className) { + $message .= ': ' . $className; + } + parent::__construct($message); + } +} diff --git a/class/Exception/ResourceNotFound.php b/class/Exception/ResourceNotFound.php new file mode 100644 index 0000000..d1b96c2 --- /dev/null +++ b/class/Exception/ResourceNotFound.php @@ -0,0 +1,36 @@ +. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +namespace carousel\Exception; + +class ResourceNotFound extends \Exception +{ + + public function __construct($id = null) + { + if ($id) { + parent::__construct('Resource not found: ' . $id); + } else { + parent::__construct('Resource not found'); + } + } + +} diff --git a/class/Factory/BaseFactory.php b/class/Factory/BaseFactory.php new file mode 100644 index 0000000..105ca41 --- /dev/null +++ b/class/Factory/BaseFactory.php @@ -0,0 +1,43 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Factory; + +use carousel\Exception\ResourceNotFound; + +/** + * + * @author Matthew McNaney + */ +abstract class BaseFactory extends \phpws2\ResourceFactory +{ + + abstract public function build(); + + public function load(int $id) + { + if (empty($id)) { + throw new ResourceNotFound; + } + $resource = $this->build(); + $resource->setId($id); + if (!parent::loadByID($resource)) { + throw new ResourceNotFound($id); + } + return $resource; + } + + public function save(\phpws2\Resource $resource) { + return self::saveResource($resource); + } + +} diff --git a/class/Factory/CarouselFactory.php b/class/Factory/CarouselFactory.php new file mode 100644 index 0000000..be7760e --- /dev/null +++ b/class/Factory/CarouselFactory.php @@ -0,0 +1,73 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Factory; + +use Canopy\Request; +use phpws2\Database; +use carousel\Resource\CarouselResource as Resource; +use carousel\Factory\SlideFactory; + +class CarouselFactory extends BaseFactory +{ + + public function build() + { + $resource = new Resource; + return $resource; + } + + public function post(Request $request) + { + $carousel = $this->build(); + $carousel->title = $request->pullPostString('title'); + $carousel->iterations = $request->pullPostInteger('iterations'); + $carousel->intervalTime = $request->pullPostInteger('intervalTime'); + $carousel->indicator = $request->pullPostInteger('indicator'); + $carousel->controls = $request->pullPostBoolean('controls'); + $carousel->pause = $request->pullPostBoolean('pause'); + return $carousel; + } + + public function listing(array $options=[]) + { + $db = Database::getDB(); + $tbl = $db->addTable('caro_carousel'); + $tbl->addOrderBy('title'); + $result = $db->select(); + return $result; + } + + public function put(int $id, Request $request) + { + $carousel = $this->load($id); + $carousel->title = $request->pullPutString('title'); + $carousel->iterations = $request->pullPutInteger('iterations'); + $carousel->intervalTime = $request->pullPutInteger('intervalTime'); + $carousel->indicator = $request->pullPutInteger('indicator'); + $carousel->controls = $request->pullPutBoolean('controls'); + $carousel->pause = $request->pullPutBoolean('pause'); + return $carousel; + } + + public function delete(Resource $carousel) + { + $slideFactory = new SlideFactory; + $slideFactory->deleteByCarouselId($carousel->id); + $db = Database::getDB(); + $tbl = $db->addTable('caro_carousel'); + $tbl->addFieldConditional($carousel->id); + $db->setLimit(1); + $db->delete(); + } + +} diff --git a/class/Factory/SlideFactory.php b/class/Factory/SlideFactory.php new file mode 100644 index 0000000..729d8c2 --- /dev/null +++ b/class/Factory/SlideFactory.php @@ -0,0 +1,95 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Factory; + +use Canopy\Request; +use phpws2\Database; +use carousel\Resource\SlideResource as Resource; + +class SlideFactory extends BaseFactory +{ + + public function build() + { + $resource = new Resource; + return $resource; + } + + public function deleteByCarouselId(int $carouselId) + { + if (empty($carouselId)) { + throw new \Exception('Carousel id is empty'); + } + + $db = Database::getDB(); + $tbl = $db->addTable('caro_slide'); + $tbl->addFieldConditional('carouselId'); + $db->delete(); + } + + public function listing(array $options = []) + { + $db = Database::getDB(); + $tbl = $db->addTable('caro_slide'); + $tbl->addOrderBy('queue'); + $result = $db->select(); + return $result; + } + + public function delete(Resource $slide) + { + + } + + public function upload($carouselId) + { + var_dump($_FILES);exit; + static $imageTypes = array('image/jpeg', 'image/png', 'image/gif'); + static $videoTypes = array('video/webm', 'video/mp4'); + if (!isset($_FILES) || empty($_FILES)) { + return array('error' => 'No files uploaded'); + } + $file = $_FILES['file']; + var_dump($file);exit; + + try { + if (!in_array($pic['type'], $imageTypes)) { + throw new \properties\Exception\WrongImageType; + } + $this->resize($pic['tmp_name']); + $title = $this->createTitleFromFileName($pic['tmp_name']); + $size = getimagesize($pic['tmp_name']); + $photo->width = $size[0]; + $photo->height = $size[1]; + $photo->title = $title; + $photo->path = $this->moveImage($pic, $owner_id); + $this->makeThumbnail($photo); + $photo->porder = $this->getMaxOrder($photo->{$this->item_column}) + 1; + self::saveResource($photo); + $result['photo'] = $photo->getStringVars(); + $result['photo']['thumbnail'] = $photo->getThumbnail(); + $result['success'] = true; + } catch (properties\Exception\FileSaveFailure $e) { + $result['success'] = false; + $result['error'] = $e->getMessage(); + } catch (properties\Exception\WrongImageType $e) { + $result['success'] = false; + $result['error'] = $e->getMessage(); + } catch (\Exception $e) { + $result['success'] = false; + $result['error'] = $e->getMessage(); + } + return $result; + } + +} diff --git a/class/Resource/AbstractResource.php b/class/Resource/AbstractResource.php new file mode 100644 index 0000000..d9c95a7 --- /dev/null +++ b/class/Resource/AbstractResource.php @@ -0,0 +1,56 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Resource; + +use \phpws2\Database; +use carousel\Exception\MissingInput; + +abstract class AbstractResource extends \phpws2\Resource +{ + + public function __set($name, $value) + { + if ((!$this->$name->allowNull() && + (method_exists($this->$name, 'allowEmpty') && !$this->$name->allowEmpty())) && + ( (is_string($value) && $value === '') || is_null($value))) { + throw new MissingInput("$name may not be empty"); + } + + $method_name = self::walkingCase($name, 'set'); + if (method_exists($this, $method_name)) { + return $this->$method_name($value); + } else { + return $this->$name->set($value); + } + } + + public function __get($name) + { + $method_name = self::walkingCase($name, 'get'); + if (method_exists($this, $method_name)) { + return $this->$method_name(); + } else { + return $this->$name->get(); + } + } + + public function __isset($name) + { + return isset($this->$name); + } + + public function isEmpty($name) + { + return $this->$name->isEmpty(); + } +} diff --git a/class/Resource/CarouselResource.php b/class/Resource/CarouselResource.php new file mode 100644 index 0000000..57c951f --- /dev/null +++ b/class/Resource/CarouselResource.php @@ -0,0 +1,46 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Resource; +use phpws2\Resource; +use carousel\Resource\AbstractResource; + +class CarouselResource extends AbstractResource +{ + + protected $id; + protected $title; + protected $iterations; + protected $intervalTime; + protected $indicator; + protected $controls; + protected $pause; + protected $lockDimensions; + + protected $table = 'caro_carousel'; + + public function __construct() + { + parent::__construct(); + $this->title = new \phpws2\Variable\StringVar('', 'title'); + $this->iterations = new \phpws2\Variable\IntegerVar(2, 'iterations'); + $this->iterations->setRange(0,3); + $this->intervalTime = new \phpws2\Variable\IntegerVar(4, 'intervalTime'); + $this->intervalTime->setRange(1,10); + $this->indicator = new \phpws2\Variable\IntegerVar(0, 'indicator'); + $this->indicator->setRange(0,3); + $this->controls = new \phpws2\Variable\BooleanVar(true, 'controls'); + $this->pause = new \phpws2\Variable\BooleanVar(true, 'pause'); + $this->lockDimensions = new \phpws2\Variable\BooleanVar(true, 'lockDimensions'); + } + +} diff --git a/class/Resource/SlideResource.php b/class/Resource/SlideResource.php new file mode 100644 index 0000000..5b9da31 --- /dev/null +++ b/class/Resource/SlideResource.php @@ -0,0 +1,66 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Resource; + +use phpws2\Resource; +use carousel\Resource\AbstractResource; + +class SlideResource extends AbstractResource +{ + + protected $title; + protected $show_title; + protected $filepath; + protected $caption; + protected $queue; + protected $url; + + /** + * 0 = Center + * 1 = Top left + * 2 = Top right + * 3 = Bottom left + * 4 = Bottom right + * @var integer + */ + protected $caption_zone; + protected $active; + protected $width; + protected $height; + protected $type; + protected $table = 'caro_slide'; + + public function __construct() + { + parent::__construct(); + $this->title = new \phpws2\Variable\TextOnly(null, 'title'); + $this->show_title = new \phpws2\Variable\BooleanVar(1, 'show_title'); + $this->filepath = new \phpws2\Variable\FileVar(null, 'filepath'); + $this->caption = new \phpws2\Variable\StringVar(null, 'caption'); + $this->caption->allowEmpty(true); + $this->caption->setInputType('textarea'); + $this->queue = new \phpws2\Variable\IntegerVar(0, 'queue'); + $this->active = new \phpws2\Variable\BooleanVar(0, 'active'); + $this->url = new \phpws2\Variable\StringVar(null, 'url'); + $this->url->allowEmpty(1); + $this->url->setInputType('textarea'); + $this->caption_zone = new \phpws2\Variable\IntegerVar(0, 'caption_zone'); + $this->width = new \phpws2\Variable\IntegerVar(0, 'width'); + $this->width->setRange(0, 5000); + $this->height = new \phpws2\Variable\IntegerVar(0, 'height'); + $this->height->setRange(0, 5000); + $this->type = new \phpws2\Variable\IntegerVar(0, 'type'); + $this->type->setRange(0,2); + } + +} diff --git a/class/Role/Admin.php b/class/Role/Admin.php new file mode 100644 index 0000000..444401e --- /dev/null +++ b/class/Role/Admin.php @@ -0,0 +1,24 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Role; +use carousel\Factory\VisitorFactory; + +class Admin +{ + + public function isAdmin() + { + return true; + } + +} diff --git a/class/Role/Base.php b/class/Role/Base.php new file mode 100644 index 0000000..e7f47a3 --- /dev/null +++ b/class/Role/Base.php @@ -0,0 +1,55 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Role; + +abstract class Base +{ + + /** + * Id of user role. Anonymous users will have id = 0 + * @var integer + */ + protected $id; + + public function __construct($id = null) + { + $this->id = (int) $id; + } + + public function isAdmin() + { + return false; + } + + public function isUser() + { + return false; + } + + public function getId() + { + return $this->id; + } + + public function getType() + { + switch (1) { + case $this->isAdmin(): + return 'admin'; + + case $this->isUser(): + return 'user'; + } + } + +} diff --git a/class/Role/User.php b/class/Role/User.php new file mode 100644 index 0000000..9b42809 --- /dev/null +++ b/class/Role/User.php @@ -0,0 +1,21 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\Role; + +class User extends Base +{ + public function isUser() + { + return true; + } +} diff --git a/class/View/AbstractView.php b/class/View/AbstractView.php new file mode 100644 index 0000000..8318f6e --- /dev/null +++ b/class/View/AbstractView.php @@ -0,0 +1,102 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\View; + +use Canopy\Request; +use phpws2\Template; + +abstract class AbstractView +{ + const directory = PHPWS_SOURCE_DIR . 'mod/carousel/'; + const http = PHPWS_SOURCE_HTTP . 'mod/carousel/'; + + protected $factory; + + + protected function getDirectory() { + return self::directory; + } + + protected function getHttp() { + return self::http; + } + + private function addScriptVars($vars) + { + if (empty($vars)) { + return null; + } + foreach ($vars as $key => $value) { + if (is_array($value)) { + $varList[] = "const $key = " . json_encode($value) . ';'; + } else { + $varList[] = "const $key = '$value';"; + } + } + return ''; + } + + protected function getScript($scriptName) + { + $jsDirectory = $this->getHttp() . 'javascript/'; + if (CAROUSEL_SYSTEM_SETTINGS['productionMode']) { + $path = $jsDirectory . 'build/' . $this->getAssetPath($scriptName); + } else { + $path = "{$jsDirectory}dev/$scriptName.js"; + } + $script = ""; + return $script; + } + + protected function getAssetPath($scriptName) + { + if (!is_file($this->getDirectory() . 'assets.json')) { + exit('Missing assets.json file. Run "npm run build" in the stories directory.'); + } + $jsonRaw = file_get_contents($this->getDirectory() . 'assets.json'); + $json = json_decode($jsonRaw, true); + if (!isset($json[$scriptName]['js'])) { + throw new \Exception('Script file not found among assets.'); + } + return $json[$scriptName]['js']; + } + + /** + * + * @staticvar boolean $vendor_included + * @param string $view_name + * @param boolean $add_anchor + * @param array $vars + * @return string + */ + public function scriptView($view_name, $add_anchor = true, $vars = null) + { + static $vendor_included = false; + if (!$vendor_included) { + $script[] = $this->getScript('vendor'); + $vendor_included = true; + } + if (!empty($vars)) { + $script[] = $this->addScriptVars($vars); + } + $script[] = $this->getScript($view_name); + $react = implode("\n", $script); + \Layout::addJSHeader($react); + if ($add_anchor) { + $content = << +EOF; + return $content; + } + } +} diff --git a/class/View/CarouselView.php b/class/View/CarouselView.php new file mode 100644 index 0000000..007b416 --- /dev/null +++ b/class/View/CarouselView.php @@ -0,0 +1,21 @@ + + * @license https://opensource.org/licenses/MIT + */ +namespace carousel\View; + +class CarouselView extends AbstractView +{ + public function listing() + { + + } + +} diff --git a/class/View/SlideView.php b/class/View/SlideView.php new file mode 100644 index 0000000..96221a7 --- /dev/null +++ b/class/View/SlideView.php @@ -0,0 +1,18 @@ + + * @license https://opensource.org/licenses/MIT + */ + +namespace carousel\View; + +class SlideView extends AbstractView +{ + +} diff --git a/config/defines.dist.php b/config/defines.dist.php new file mode 100644 index 0000000..9b8a5b5 --- /dev/null +++ b/config/defines.dist.php @@ -0,0 +1,20 @@ + + * @license https://opensource.org/licenses/MIT + */ +/** + * Do not change the below unless developing + */ +define('CAROUSEL_SYSTEM_SETTINGS', + [ + 'productionMode' => true, + 'friendlyErrors' => true + ] +); diff --git a/config/defines.php b/config/defines.php new file mode 100644 index 0000000..a0fd784 --- /dev/null +++ b/config/defines.php @@ -0,0 +1,20 @@ + + * @license https://opensource.org/licenses/MIT + */ +/** + * Do not change the below unless developing + */ +define('CAROUSEL_SYSTEM_SETTINGS', + [ + 'productionMode' => false, + 'friendlyErrors' => false + ] +); diff --git a/exports.js b/exports.js new file mode 100644 index 0000000..a5ea65c --- /dev/null +++ b/exports.js @@ -0,0 +1,8 @@ +/* global __dirname, exports */ +exports.path = require('path') +exports.APP_DIR = exports.path.resolve(__dirname, 'javascript') + +exports.entry = { + Carousel: exports.APP_DIR + '/Carousel/index.jsx', + Slide: exports.APP_DIR + '/Slide/index.jsx', +} diff --git a/javascript/Carousel/Form.jsx b/javascript/Carousel/Form.jsx new file mode 100644 index 0000000..f20105c --- /dev/null +++ b/javascript/Carousel/Form.jsx @@ -0,0 +1,149 @@ +'use strict' +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import ButtonGroup from '@essappstate/canopy-react-buttongroup' +import BigCheckbox from '@essappstate/canopy-react-bigcheckbox' + +export default class Form extends Component { + constructor(props) { + super(props) + } + + intervalTime() { + const {resource, update} = this.props + const buttons = [ + { + value: '2', + label: (2) + }, { + value: '4', + label: (4) + }, { + value: '6', + label: (6) + }, { + value: '8', + label: (8) + }, { + value: '10', + label: (10) + } + ] + return + } + + iterations() { + const {resource, update} = this.props + const buttons = [ + { + value: '0', + label: ( + + ) + }, { + value: '1', + label: (1) + }, { + value: '2', + label: (2) + }, { + value: '3', + label: (3) + } + ] + return + } + + indicator() { + const {resource, update} = this.props + const buttons = [ + { + value: '0', + label: (None) + }, { + value: '1', + label: ( +  Bullets) + }, { + value: '2', + label: ( + +  Thumbnails + ) + } + ] + return + } + + controls() { + const {resource, update} = this.props + return + } + + pause() { + const {resource, update} = this.props + return + } + + render() { + const {resource, save, update} = this.props + const disableSave = resource && resource.title.length === 0 + return ( +
+ + +
+
+ +
{this.iterations()}
+
+
+ {this.intervalTime()} +
+
+ {this.indicator()} +
+
+
{this.controls()}
+
{this.pause()}
+
+
+ +
+ ) + } +} +Form.propTypes = { + resource: PropTypes.object, + finish: PropTypes.func, + save: PropTypes.func, + update: PropTypes.func, +} diff --git a/javascript/Carousel/index.jsx b/javascript/Carousel/index.jsx new file mode 100644 index 0000000..8f0d325 --- /dev/null +++ b/javascript/Carousel/index.jsx @@ -0,0 +1,100 @@ +'use strict' +import React from 'react' +import ReactDOM from 'react-dom' +import Listing from '../Extends/Listing' +import Form from './Form' + +export default class Carousel extends Listing { + constructor(props) { + super(props) + this.module = 'carousel' + this.role = 'Admin' + this.control = 'Carousel' + this.label = 'Carousel' + this.form = this.form.bind(this) + this.defaultResource = { + id: 0, + title: '', + iterations: '2', + intervalTime: '4', + indicator: '0', + controls: true, + pause: true + } + this.columns = [ + { + column: 'title', + label: 'Title' + } + ] + this.contextMenu = [ + { + handleClick: this.command.bind(this), + data: { + command: 'edit' + }, + label: ( + +  Edit Carousel + ) + }, { + handleClick: this.command.bind(this), + data: { + command: 'slides' + }, + label: ( + +  Update Slides + ) + }, { + handleClick: this.command.bind(this), + data: { + command: 'delete' + }, + label: ( + +  Delete Carousel + ) + } + ] + this.state.resource = this.defaultResource + } + + command(event, data) { + event.preventDefault() + switch (data.command) { + case 'edit': + this.editResource(data.name) + break + + case 'delete': + this.deleteResource(data.name) + break + + case 'slides': + location.href = './carousel/Admin/Slide/?carousel=' + this.state.listing[data.name].id + break + } + } + + form() { + return ( +
+ ) + } + + overlay() { + return { + content:
{this.form()}
, + width: '80%', + title: 'Edit Carousel', + close: this.load + } + } + +} +ReactDOM.render(, document.getElementById('Carousel')) diff --git a/javascript/Extends/Grid.jsx b/javascript/Extends/Grid.jsx new file mode 100644 index 0000000..5c9bffa --- /dev/null +++ b/javascript/Extends/Grid.jsx @@ -0,0 +1,125 @@ +'use strict' +import React from 'react' +import PropTypes from 'prop-types' +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' +import {faCaretUp, faCaretDown} from '@fortawesome/free-solid-svg-icons' +import {ContextMenu, MenuItem, ContextMenuTrigger} from "react-contextmenu" +import './grid.scss' + +const Grid = ({listing, columns, sortFunction, currentSort, contextMenu}) => { + const sortIconTrack = {} + columns.forEach((value) => { + sortIconTrack[value.column] = 0 + }) + if (currentSort.sortBy) { + sortIconTrack[currentSort.sortBy] = currentSort.sortByDir + } + + const MENU_TYPE = 'SIMPLE' + const collect = (props) => { + return {name: props.name} + } + + const headers = (columns, sortFunction) => { + const th = columns.map((value, key) => { + let icon + let className = [] + if (sortIconTrack[value.column] === 1) { + className.push('pointer') + icon = + } else if (sortIconTrack[value.column] === 2) { + className.push('pointer') + icon = + } + if (value.className) { + className.push('bg-primary') + } + + let style + if (value.style && typeof value.style === 'object') { + style = value.style + } + + return ( + {value.label} {icon} + + ) + }) + return ({th}) + } + let tableClass = 'table table-striped table-hover' + + let menu + if (contextMenu) { + let menuOptions = contextMenu.map((value, key) => { + return ( + + {value.label} + + ) + }) + menu = ( + + {menuOptions} + + ) + } + + let rows = listing.map((resource, key) => { + let stack = columns.map((value, subkey) => { + if (value.callback) { + return {value.callback(resource)} + } else { + return {resource[value.column]} + } + }) + + if (contextMenu) { + return ( + + {stack} + + ) + } else { + return ( + {stack} + ) + } + }) + + return ( +
+ + + {headers(columns, sortFunction)} + {rows} + +
+ {menu} +
+ ) +} + +Grid.propTypes = { + listing: PropTypes.array, + columns: PropTypes.array, + sortFunction: PropTypes.func, + currentSort: PropTypes.object, + contextMenu: PropTypes.array +} + +Grid.defaultProps = { + contextMenu: null +} +export default Grid diff --git a/javascript/Extends/Listing.jsx b/javascript/Extends/Listing.jsx new file mode 100644 index 0000000..e5ae285 --- /dev/null +++ b/javascript/Extends/Listing.jsx @@ -0,0 +1,355 @@ +'use strict' +import React, {Component} from 'react' +import {Navbar, NavbarButton, NavbarSearch} from '@essappstate/react-navbar' +import Waiting from '@essappstate/react-waiting' +import Grid from './Grid' +import Overlay from '@essappstate/canopy-react-overlay' +/* global $ */ + +export function debounce(fn, delay) { + var timer = null + return function () { + var context = this, + args = arguments + clearTimeout(timer) + timer = setTimeout(function () { + fn.apply(context, args) + }, delay) + } +} + +export default class Listing extends Component { + delayLoad() { + this.load() + } + + constructor(props) { + super(props) + this.state = { + search: '', + listing: [], + loading: true, + message: null, + messageType: 'danger', + overlay: false, + resource: {} + } + this.module = 'module' + this.role = 'role' + this.control = 'control' + this.label = 'label' + this.sortBy = null + this.sortByDir = 0 + this.defaultResource = {} + this.save = this.save.bind(this) + this.showGrid = this.showGrid.bind(this) + this.load = this.load.bind(this) + this.reset = this.reset.bind(this) + this.sortByColumn = this.sortByColumn.bind(this) + this.handle = this.handle.bind(this) + this.overlayOn = this.overlayOn.bind(this) + this.overlayOff = this.overlayOff.bind(this) + this.editResource = this.editResource.bind(this) + this.delayLoad = debounce(this.delayLoad, 1000) + this.update = this.update.bind(this) + } + + componentDidMount() { + this.load() + this.setState({resource: this.defaultResource}) + } + + finish() { + this.load() + this.overlayOff() + } + + overlayOn() { + this.setState({overlay: true}) + } + + overlayOff() { + this.setState({overlay: false}) + } + + loadResource(key) { + const resource = Object.assign({}, this.state.listing[key]) + this.setState({resource}) + } + + editResource(key) { + this.loadResource(key) + this.overlayOn() + } + + deleteResource(key) { + const resource = this.state.listing[key] + if (confirm('Are you sure you want to delete this carousel along with all it\'s slides?')) { + $.ajax({ + url: this.getUrl() + '/' + resource.id, + dataType: 'json', + type: 'delete', + success: ()=>{ + this.setMessage(this.label + ' deleted.', 'success') + this.load() + }, + error: ()=>{ + this.setMessage('Sorry. An error prevented deleting the ' + this.label, 'danger') + } + }) + } + } + + handle(varname, value) { + if (typeof value === 'object') { + value = value.target.value + } + const resource = this.state.resource + resource[varname] = value + this.setState({resource}) + } + + reset() { + this.overlayOff() + this.setState({resource: Object.assign({}, this.defaultResource)}) + } + + getSearch() { + return ( + { + this.updateSearch(e.target.value) + }}/> + ) + } + + updateSearch(search) { + this.setState({search}) + + if (search.length > 2 || search.length === 0) { + this.delayLoad() + } + } + + sortByColumn(column) { + if (column === this.sortBy) { + switch (this.sortByDir) { + case 0: + this.sortByDir = 1 + break + + case 1: + this.sortByDir = 2 + break + + case 2: + this.sortByDir = 0 + this.sortBy = null + } + } else { + this.sortByDir = 1 + this.sortBy = column + } + + this.load() + } + + getUrl() { + return `${this.module}/${this.role}/${this.control}` + } + + load() { + const url = this.getUrl() + $.ajax({ + url, + data: { + search: this.state.search, + sortBy: this.sortBy, + sortByDir: this.sortByDir + }, + dataType: 'json', + type: 'get', + success: (data) => { + this.setState({listing: data.listing, loading: false}) + }, + error: () => { + this.setState({loading: false}) + this.setMessage('Could not access server') + } + }) + } + + showGrid() {} + + setMessage(message, messageType = 'danger') { + setTimeout(() => { + this.clearMessage() + }, 5000) + + this.setState({message, messageType}) + } + + clearMessage() { + this.setState({message: null, messageType: 'danger'}) + } + + save() { + let type = 'post' + let url = this.getUrl() + if (this.state.resource.id !== '0') { + url = url + '/' + this.state.resource.id + type = 'put' + } + $.ajax({ + url, + data: { + ...this.state.resource + }, + dataType: 'json', + type, + success: () => { + this.load() + this.setMessage( +
+  Save successful.
, + 'success' + ) + }, + error: () => { + this.setMessage( +
+  Unable to save {this.label}.
+ ) + }, + complete: () => this.reset() + + }) + } + + message() { + const cn = `alert alert-${this.state.messageType} alert-dismissible fade show` + if (this.state.message) { + return ( +
+ {this.state.message} + +
+ ) + } + } + + navbarButton() { + const label = ( + +  Create + ) + const button = ( + + ) + return button + } + + update(varname, value) { + let changeValue + if (typeof value === 'object') { + changeValue = value.target.value + } else { + changeValue = value + } + const {resource} = this.state + resource[varname] = changeValue + this.setState({resource}) + } + + navbar() { + const button = this.navbarButton() + + const search = this.getSearch() + return + } + + title() { + return null + } + + showOverlay() { + const overlay = this.overlay() + if (overlay === null) { + return + } + + let width + if (overlay.width) { + width = overlay.width + } + + let title + if (overlay.title) { + title = overlay.title + } + + let close + if (overlay.close) { + close = () => { + overlay.close() + this.overlayOff() + } + } else { + close = this.overlayOff + } + + return ( + {overlay.content} + ) + + } + + content() { + if (this.state.loading) { + return
+ } + if (this.state.listing.length === 0) { + const content = [] + content.push(No {this.label.toLowerCase()}s found.) + if (this.state.search.length > 0) { + content.push( You may want to broaden your search.) + } + return content + } + return ( + + ) + } + + render() { + return ( +
+
{this.showOverlay()}
+
{this.navbar()}
+
{this.message()}
+
{this.title()}
+
{this.content()}
+
+ ) + } +} diff --git a/javascript/Extends/grid.scss b/javascript/Extends/grid.scss new file mode 100644 index 0000000..587b29e --- /dev/null +++ b/javascript/Extends/grid.scss @@ -0,0 +1,12 @@ +.context { + cursor: context-menu; +} + +nav.react-contextmenu { + border : 1px solid rgb(89, 89, 89); + border-radius: 4px; + padding: 6px; + background-color : rgba(255,255,255,.9); + box-shadow: 5px 5px 5px rgb(157, 157, 157,); + min-width : 100px; +} diff --git a/javascript/Slide/Form.js b/javascript/Slide/Form.js new file mode 100644 index 0000000..80648ee --- /dev/null +++ b/javascript/Slide/Form.js @@ -0,0 +1,102 @@ +'use strict' +import React from 'react' +import PropTypes from 'prop-types' +import Dropzone from 'react-dropzone-uploader' +import BigCheckbox from '@essappstate/canopy-react-bigcheckbox' +import './style.scss' +import 'react-dropzone-uploader/dist/styles.css' + +const Form = ({close, update, resource, save, upload}) => { + + const uploadPrompt = ( +
+
Click to browse
- or -
drag image or video file here.
+
+ ) + + return ( +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+