├── .gitattributes ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── package ├── assets │ ├── css │ │ ├── app.css │ │ ├── lib.css │ │ ├── setup.css │ │ └── vue.css │ ├── fonts │ │ ├── font-awesome │ │ │ ├── font-awesome.min.css │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ └── lato │ │ │ ├── lato-bold-webfont.woff │ │ │ ├── lato-italic-webfont.woff │ │ │ ├── lato-regular-webfont.woff │ │ │ └── lato.css │ ├── images │ │ ├── ajax-loader-bars.gif │ │ ├── ajax-loader-grey.gif │ │ ├── ajax-loader.gif │ │ └── favicon.ico │ └── js │ │ ├── app.js │ │ ├── iframe.js │ │ ├── lib.js │ │ └── setup.js ├── config │ ├── admin.json │ └── settings.json ├── setup │ ├── assets │ │ ├── favicon.ico │ │ ├── scripts.js │ │ └── styles.css │ ├── controllers │ │ └── ExampleController.php │ └── views │ │ ├── example │ │ └── index.blade.php │ │ ├── head.blade.php │ │ ├── help.blade.php │ │ └── home.blade.php ├── templates │ ├── controller.txt │ └── method.txt └── views │ ├── help │ ├── helpers │ │ ├── code.blade.php │ │ ├── icon.blade.php │ │ ├── index.md │ │ ├── list.blade.php │ │ └── table.blade.php │ ├── methods │ │ ├── comments.blade.php │ │ ├── contents.blade.php │ │ ├── index.md │ │ ├── parameters.blade.php │ │ ├── run.blade.php │ │ ├── test.blade.php │ │ ├── typecasting.blade.php │ │ ├── typehinting.blade.php │ │ └── variables.blade.php │ ├── output │ │ ├── blade.blade.php │ │ ├── form.blade.php │ │ ├── formatting.md │ │ ├── index.md │ │ ├── links.blade.php │ │ ├── markdown.blade.php │ │ ├── pagination.blade.php │ │ ├── vue-text.blade.php │ │ └── vue.vue │ ├── setup │ │ ├── assets.md │ │ ├── controllers.blade.php │ │ ├── head.md │ │ ├── index.md │ │ ├── livereload.md │ │ ├── middleware.md │ │ ├── pages.md │ │ ├── permissions.md │ │ ├── settings.md │ │ └── views.md │ ├── tags │ │ ├── css.blade.php │ │ ├── field.blade.php │ │ ├── iframe.blade.php │ │ ├── index.md │ │ ├── order.blade.php │ │ └── warn.blade.php │ └── tools │ │ ├── cat.blade.php │ │ ├── folder.blade.php │ │ ├── index.md │ │ ├── phpinfo.blade.php │ │ └── routes.vue │ ├── html │ ├── list.blade.php │ └── table.blade.php │ ├── index.blade.php │ ├── no-help.blade.php │ ├── no-home.blade.php │ ├── no-setup.blade.php │ ├── setup.blade.php │ └── sketchpad-head.blade.php └── src ├── SketchpadServiceProvider.php ├── commands └── InstallCommand.php ├── config ├── InstallerSettings.php ├── Paths.php ├── SketchpadConfig.php └── SketchpadSettings.php ├── controllers ├── ApiController.php ├── SetupController.php └── SketchpadController.php ├── help ├── demo │ └── ToolsController.php └── docs │ ├── HelpersController.php │ ├── MethodsController.php │ ├── OutputController.php │ ├── SetupController.php │ └── TagsController.php ├── objects ├── file │ ├── File.php │ └── Folder.php ├── install │ ├── ClassTemplate.php │ ├── Composer.php │ ├── Copier.php │ ├── FilesystemObject.php │ ├── Folder.php │ ├── JSON.php │ ├── NamespaceResolver.php │ └── Template.php ├── reflection │ ├── Comment.php │ ├── Controller.php │ ├── ControllerError.php │ ├── Field.php │ ├── Method.php │ ├── Parameter.php │ └── Tag.php ├── route │ ├── CallReference.php │ ├── ControllerErrorReference.php │ ├── ControllerReference.php │ ├── FolderReference.php │ └── RouteReference.php └── scanners │ ├── AbstractScanner.php │ ├── Finder.php │ └── Scanner.php ├── routes.php ├── services ├── Installer.php ├── Router.php ├── Setup.php └── Sketchpad.php ├── traits ├── GetterTrait.php ├── ReflectionTraits.php └── SaveFileTrait.php └── utils ├── Code.php ├── Html.php ├── Options.php └── helpers.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /build export-ignore 2 | /resources export-ignore 3 | /.gitignore export-ignore 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | 6 | Note that like Laravel, Laravel Sketchpad *doesn't* adhere to semver; both use: 7 | 8 | - major versions for generations 9 | - minor versions for breaking changes 10 | - patch versions for features, nightly builds, and patches 11 | 12 | ## [1.2.*] - 2017-05-30 13 | 14 | ### Added 15 | 16 | - Added functionality to load custom views from a subfolder 17 | - Added static `Code` helper class 18 | - Added `icon()` helper function 19 | - Added icon functionality to `tb()` helper 20 | - Added text/data `type` functionality to `tb()` helper 21 | - Added column ordering to `tb()` helper 22 | - Added support to render `Paginator` instances to `tb()` helper 23 | - Added support for middleware 24 | 25 | ### Changed 26 | 27 | - Made Paths utility functions static 28 | - Moved demo tools controller before docs controller 29 | - `code()` is now a variadic function and calls `Code` class 30 | - Improved handling of no data in `tb()` helper 31 | - `text()` helper now converts HTML entities 32 | - `Html` class methods now `return` output, with helpers `echo`ing it 33 | 34 | ### Fixed 35 | 36 | - Modified test mode so it passes `$run` even if false 37 | - Modified custom views to load directly 38 | - Fixed security hole in setting per-user permissions 39 | - Fixed bug with windows paths in Setup 40 | 41 | 42 | ## [1.2.0] - 2017-05-09 43 | 44 | ### Added 45 | 46 | - Added top-level Search and Favourites 47 | - Implemented support for `@field` tags 48 | - New `text()` and `code()` helper functions 49 | - Data injection for `md()` function 50 | - Static `Sketchpad::$form` and `Sketchpad::$data` properties 51 | - Automatic injection of `$route` variable into views 52 | - Implemented controller reordering 53 | - Implemented support for implicit pagination 54 | - Included app controllers folder in paths 55 | - Custom page title functionality 56 | - Added custom Help page 57 | - Added custom head view 58 | - Implemented live-reloading for custom pages 59 | - Added index pages for all help controllers 60 | - Added scroll to section functionality for Settings 61 | - Bootstrap formatting for Markdown tables 62 | - Added favicon.ico 63 | - Added changelog 64 | 65 | ### Changed 66 | 67 | - Huge docs and examples update! 68 | - Created new Sketchpad docs sections 69 | - Updated a lot of help to be better-formatted and more consistent 70 | - Improved example controller and view content 71 | - Moved various settings to single "Site" section 72 | - Moved custom asset declaration to custom head view 73 | - Disabled Settings page now shows a message 74 | - Index pages can be re-run 75 | - Added formatting classes to `pr()` and `vd()` 76 | - Updated FontAwesome to latest version 77 | - Improved submission of form data 78 | - Improved formatting of tables and forms 79 | - Removed global appendOutput setting 80 | - New Param sub-components for different data types 81 | - Simplified copying of user views in setup 82 | 83 | ### Fixed 84 | 85 | - Defended against showing parent paths in Browse filesystem 86 | - Defended against saving settings if not allowed in admin.json 87 | - Fixed edge-case with buggy internal routes 88 | - Fixed bug with method titles showing parameters 89 | - Fixed Markdown not showing code or blockquotes correctly 90 | - Fixed bug where coloured @icon icons not showing 91 | - Fixed scroll to top animation 92 | - Fixed bug with intercepted link currentTarget 93 | - Fixed URL encoding bug with route parameters 94 | - Fixed bug in NumberParam where unset variables don't show 0 95 | - Fixed bug where Output only styles first markdown table 96 | - Fixed bug in internal href links not working for links with hashes 97 | - Fixed bug with validate-path directive causing settings to be reloaded on focus 98 | - Fixed routing bug in Help 99 | - Removed absolute path references from RouteReference classes 100 | - Updated demo link in readme 101 | 102 | ### Upgrade notes 103 | 104 | The new settings configuration has breaking changes: 105 | 106 | - Back up your existing settings file `storage/sketchpad/settings.json` 107 | - Reinstall Sketchpad via `http:///sketchpad/setup` 108 | - Locate updated `settings.json` and update relevant nodes (`paths`, `livereload`, `ui`) from your backed-up file 109 | 110 | 111 | ## [1.1.0] - 2017-04-21 112 | 113 | ### Added 114 | 115 | - Test mode functionality 116 | - Clientside script to auto-resize iframe 117 | - User can now edit and show custom home page 118 | - Basic admin permissions to prevent showing of Setup and Settings pages 119 | 120 | ### Changed 121 | 122 | - Moved routes to separate file 123 | - Example Browse Filesystem tool now only browses from site root downwards 124 | - Refactored `publish/` folder to be `package/` 125 | - Moved `resources/views` to `package/views` 126 | - Moved `$config` references for Example controller / view to method 127 | 128 | ### Fixed 129 | 130 | - Controller labels not being humanized 131 | - Bug with new controllers not being added 132 | - Layout fix when moving between controller indexes and methods 133 | - Prevented spellcheck on settings fields 134 | - Setup edge-case bugs for custom setups which required two install attempts 135 | - URLs now load properly into iframes 136 | - Better iframe error handling 137 | - Page titles now show for non-console routes 138 | - `tb()` and `ls()` now display booleans correctly 139 | - `vd()` now displays preformatted text, also added better formatting 140 | 141 | 142 | ## [1.0.0] - 2017-04-12 143 | 144 | ### Added 145 | 146 | - Scroll to top when changing methods 147 | - Pressing enter now submits method changes 148 | - Added icon parameter to `alert()` helper 149 | - Parameter values now stay the same when reloading 150 | - Shorthand `sketchpad:link` format for in-page links 151 | - Shorthand `$assets/` URL reference for user assets URLs 152 | 153 | ### Changed 154 | 155 | - Search page starts with no results; type to filter 156 | - Nicer console transitions 157 | - Various UI updates 158 | - Improved resilience of watcher / store / state updates for controllers / methods 159 | - Major refactor of router > console 160 | - Better error handling / feedback on session timeouts 161 | - Simplified return of different data formats by using container div rather than headers 162 | - Improved VueJS data injection functionality; now uses `$data` variable 163 | - Removed BrowserSync watching in favour of LiveReload 164 | 165 | ### Fixed 166 | 167 | - Minor bugs in table rendering 168 | - Bug with JSON data return < Laravel 5.2 169 | - Bug with wrong active path showing for paths with similar names 170 | - Various inconsistencies with controller reloading / missing routes 171 | - Various installer bugs / optmiisations 172 | 173 | ## Pre-releases 174 | 175 | - [1.0.0-beta] - 2017-03-24 176 | - [0.3.0] - 2016-05-12 177 | - [0.2.0] - 2016-04-30 178 | - [0.1.0] - 2016-04-27 179 | - 0.0.0 - 2016-04-19 180 | 181 | [1.2.*]: https://github.com/davestewart/laravel-sketchpad/compare/v1.2.0...develop 182 | [1.2.0]: https://github.com/davestewart/laravel-sketchpad/compare/v1.1.0...v1.2.0 183 | [1.1.0]: https://github.com/davestewart/laravel-sketchpad/compare/v1.0.0...v1.1.0 184 | [1.0.0]: https://github.com/davestewart/laravel-sketchpad/compare/v1.0.0-beta...v1.0.0 185 | [1.0.0-beta]: https://github.com/davestewart/laravel-sketchpad/compare/v0.3...v1.0.0-beta 186 | [0.3.0]: https://github.com/davestewart/laravel-sketchpad/compare/v0.2...v0.3 187 | [0.2.0]: https://github.com/davestewart/laravel-sketchpad/compare/v0.1...v0.2 188 | [0.1.0]: https://github.com/davestewart/laravel-sketchpad/compare/3b2c4c0efdffaaead8a490132c49bdea3a3aef1c...v0.1 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave Stewart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Sketchpad 2 | 3 | ## Introduction 4 | 5 | Sketchpad is an innovative, bolt-on development environment for your existing Laravel site. 6 | 7 | It's a place to write, test, experiment and execute code, or just a place to group useful tools and functions you want easy access to. 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/132681/24509000/14ee5b96-155d-11e7-95ed-7712a32dceb6.png) 10 | 11 | 12 | ## Overview 13 | 14 | Sketchpad provides an alternative structure of controllers, views, assets and routing that lives alongside your main Laravel installation. 15 | 16 | You can navigate methods, interactively modify parameters, and even [live-reload](https://github.com/davestewart/laravel-sketchpad-reload) the code you're working on, all from a friendly UI. 17 | 18 | Additional tooling and functionality is designed to make it quick and easy to develop or debug new tools, features and code. 19 | 20 | ## Demo 21 | 22 | View the live demo at: 23 | 24 | - [sketchpad.davestewart.io/sketchpad](http://sketchpad.davestewart.io/sketchpad) 25 | 26 | ## Uses 27 | 28 | - Test out new coding ideas 29 | - Develop tools and utilities for everyday work 30 | - Debug applications in the same environment they're running in 31 | - Practice with new libraries or APIs with your existing code 32 | - Give new hires a place to get up to speed with the codebase 33 | - Double check how that rarely-used function actually works 34 | - Give all that "secret" or commented-out code somewhere to live 35 | - Live-document existing features and tools 36 | - Generate and manage reports 37 | 38 | 39 | ## More info 40 | 41 | For installation and usage instructions, see the [wiki](https://github.com/davestewart/laravel-sketchpad/wiki). 42 | 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "davestewart/sketchpad", 3 | "type": "library", 4 | "description": "An innovative front-end environment for interactive Laravel development", 5 | "keywords": [ 6 | "laravel", 7 | "testing", 8 | "tools", 9 | "tinker", 10 | "playground" 11 | ], 12 | "homepage": "https://github.com/davestewart/laravel-sketchpad", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Dave Stewart", 17 | "homepage": "http://davestewart.io", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.3.0", 23 | "illuminate/support": "^5.0", 24 | "symfony/filesystem": "^3.1" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "davestewart\\sketchpad\\": "src" 29 | } 30 | }, 31 | "extra": { 32 | "laravel": { 33 | "providers": [ 34 | "davestewart\\sketchpad\\SketchpadServiceProvider" 35 | ] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /package/assets/css/setup.css: -------------------------------------------------------------------------------- 1 | body{padding-top:100px;padding-bottom:100px;margin-bottom:600px;overflow-y:scroll}#setup{max-width:800px;padding:0 15px;margin:auto}#controls{padding:0 15px;text-align:right}h1,h2,h3,h4,h5,h6{color:#2c3e50}h1{font-size:60px}h3:first-child,h4:first-child{margin-top:0}h3:not(:first-child),h4:not(:first-child){margin-top:1.5em}h2,h3,h4{margin-bottom:1em;margin-top:1em}h4{color:#000;margin-top:2em}.code,.table.pre td,.table td.pre,pre{font:normal 11px/1.4em Menlo,Monaco,Consolas,Courier New,monospace!important;white-space:pre;color:#555;margin:20px}pre{border:none;margin:25px 15px;font-size:12px;background-color:#fbfbfb;color:#2c3e50;white-space:pre-wrap;word-break:break-all}code{font-size:.8em;font-size:12px}#steps{transition:all .5s;margin-top:20px}#steps article{display:none}#steps article.active{display:block}.btn[disabled]{opacity:.5;cursor:default}.btn[disabled],.btn[disabled]:hover{color:inherit!important}body.modal-open .navbar-fixed-top{margin-right:15px}.navbar-default{background-color:#ecf0f1}.navbar-default .navbar-brand{color:#2c3e50!important}fieldset{margin-bottom:1.5em}legend{margin-bottom:.3em}.inline label.radio{padding-top:0}.inline label.radio *{margin-right:3px;display:inline-block;position:relative}.inline div.radio{display:inline-block;margin-right:10px}form label{padding:4px}form textarea{max-width:100%}form .form-group-submit{margin-top:30px;margin-bottom:0}form .form-group:last-child{margin-bottom:0}form .form-control{background-color:rgba(220,228,236,.4);border:none;padding:9px 10px;font-size:inherit;height:40px}form .form-group.active .help-block.prompt,form .form-group .help-block.hint{display:none}form .form-group.active .help-block.hint,form .form-group .help-block.prompt{display:block}form .help-block{line-height:18px}form .help-block,form .help-text{font-size:.8em;padding-left:4px}form .help-block.hint{color:#000;font-family:Menlo,Monaco,Consolas,Courier New,monospace;font-size:.7em}form .form-group.has-error .help-block.hint{color:#c00}.logs{padding:10px 20px}.logs .log{position:relative;list-style:none}.logs .log .state{text-align:center;width:2%}.logs p{margin-bottom:.2em}.logs .log .help-block{font-size:.8em}.logs .log.pass svg{fill:#0c0}.logs .log.fail svg{fill:#c00}.logs code{font-size:.7em;line-height:1em;word-break:break-word}.logs pre{margin:0;padding:0;font-size:9px} -------------------------------------------------------------------------------- /package/assets/css/vue.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/css/vue.css -------------------------------------------------------------------------------- /package/assets/fonts/font-awesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/fonts/font-awesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /package/assets/fonts/font-awesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/fonts/font-awesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /package/assets/fonts/lato/lato-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/fonts/lato/lato-bold-webfont.woff -------------------------------------------------------------------------------- /package/assets/fonts/lato/lato-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/fonts/lato/lato-italic-webfont.woff -------------------------------------------------------------------------------- /package/assets/fonts/lato/lato-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/fonts/lato/lato-regular-webfont.woff -------------------------------------------------------------------------------- /package/assets/fonts/lato/lato.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Lato'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('lato-regular-webfont.woff') format('woff'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'Lato'; 10 | font-style: normal; 11 | font-weight: 700; 12 | src: url('lato-bold-webfont.woff') format('woff'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Lato'; 17 | font-style: italic; 18 | font-weight: 400; 19 | src: url('lato-italic-webfont.woff') format('woff'); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /package/assets/images/ajax-loader-bars.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/images/ajax-loader-bars.gif -------------------------------------------------------------------------------- /package/assets/images/ajax-loader-grey.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/images/ajax-loader-grey.gif -------------------------------------------------------------------------------- /package/assets/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/images/ajax-loader.gif -------------------------------------------------------------------------------- /package/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/assets/images/favicon.ico -------------------------------------------------------------------------------- /package/assets/js/iframe.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | function post() 3 | { 4 | parent.postMessage({setFrameHeight: document.body.clientHeight}, "*"); 5 | } 6 | 7 | if (parent.postMessage) 8 | { 9 | post(); 10 | window.onresize = post; 11 | } 12 | }()) -------------------------------------------------------------------------------- /package/config/admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "setup": true, 3 | "settings": true 4 | } 5 | -------------------------------------------------------------------------------- /package/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "route": "", 3 | "paths": { 4 | "controllers": [ 5 | { 6 | "name": "app", 7 | "path": "app/Http/Controllers", 8 | "enabled": false 9 | }, 10 | { 11 | "name": "sketchpad", 12 | "path": "", 13 | "enabled": true 14 | }, 15 | { 16 | "name": "help/demo", 17 | "path": "vendor/davestewart/sketchpad/src/help/demo", 18 | "enabled": true 19 | }, 20 | { 21 | "name": "help/docs", 22 | "path": "vendor/davestewart/sketchpad/src/help/docs", 23 | "enabled": true 24 | } 25 | ], 26 | "views": "", 27 | "assets": "" 28 | }, 29 | "site": { 30 | "name": "Sketchpad", 31 | "home": "welcome", 32 | "help": "help", 33 | "views": "", 34 | "search": true, 35 | "favourites": true 36 | }, 37 | "livereload": { 38 | "preset": "", 39 | "host": "", 40 | "usePolling": false, 41 | "paths": [] 42 | }, 43 | "ui": { 44 | "humanizeText": true, 45 | "showComments": true, 46 | "showArchived": true, 47 | "formatCode": true, 48 | "scrollTop": true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package/setup/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/setup/assets/favicon.ico -------------------------------------------------------------------------------- /package/setup/assets/scripts.js: -------------------------------------------------------------------------------- 1 | console.info('Welcome to sketchpad!'); 2 | -------------------------------------------------------------------------------- /package/setup/assets/styles.css: -------------------------------------------------------------------------------- 1 | /* HEADINGS */ 2 | 3 | #output h1:first-child, 4 | #output h2:first-child, 5 | #output h3:first-child, 6 | #output h4:first-child { 7 | margin-top:0; 8 | } 9 | 10 | #output h1:not(:first-child) { margin-top:1.0em; } 11 | #output h2:not(:first-child) { margin-top:1.0em; } 12 | #output h3:not(:first-child) { margin-top:1.3em; } 13 | #output h4:not(:first-child) { margin-top:1.5em; } 14 | #output h5:not(:first-child) { margin-top:1.6em; } 15 | 16 | 17 | /* TEXT */ 18 | 19 | p.note { 20 | color:#e74c3c; 21 | } 22 | 23 | p.special { 24 | padding: 5px 8px; 25 | border-radius: 5px; 26 | background: repeating-linear-gradient( 27 | -45deg, 28 | #EEE, 29 | #EEE 3px, 30 | #FFF 3px, 31 | #FFF 6px 32 | ); 33 | } 34 | 35 | 36 | /* NAVIGATION */ 37 | 38 | li.fancy { 39 | padding: 3px; 40 | border: 1px dashed red; 41 | border-radius: 7px; 42 | left: -3px; 43 | top: -3px; 44 | } 45 | 46 | li.fancy p { 47 | font-style: italic; 48 | } 49 | 50 | li.fancy.active { 51 | border: 1px solid #333; 52 | background: repeating-linear-gradient( 53 | -45deg, 54 | #222, 55 | #222 5px, 56 | #333 5px, 57 | #333 10px 58 | ); 59 | } 60 | 61 | li.fancy.active p { 62 | overflow: visible; 63 | text-overflow: clip; 64 | white-space: normal; 65 | margin: 5px 0; 66 | } 67 | 68 | /* SCALING */ 69 | 70 | @media only screen and (max-width:960px) { 71 | html { 72 | zoom: 85%; 73 | } 74 | .container-fluid { 75 | padding: 0 15px; 76 | } 77 | } 78 | 79 | @media only screen and (max-width:600px) { 80 | html { 81 | zoom: 75%; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /package/setup/controllers/ExampleController.php: -------------------------------------------------------------------------------- 1 | Overview 2 |

This is the index method for your installed ExampleController.

3 |

Like all the other controllers on the left, its methods are shown as a list; you click them, they run :)

4 |

To add or remove controller folders, see the Paths section of the Settings page.

5 | 6 |

Source

7 |

Your source files can be found at:

8 |
 9 | {{ $config->controllers['sketchpad'] }}
10 | {{ $config->views }}
11 | {{ $config->assets }}
12 | 
13 |

These files are yours to modify, add to, or remove as you like.

14 | -------------------------------------------------------------------------------- /package/setup/views/head.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package/setup/views/help.blade.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

Help

7 |
8 |
9 |

Hello

10 |

This is your custom help page, located at {{ $help }}.

11 |

Edit it to suit your needs!

12 |
13 | -------------------------------------------------------------------------------- /package/setup/views/home.blade.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

Home

7 |
8 |
9 |

Welcome

10 |

This is your custom home page, located at {{ $home }}.

11 |

Edit it to suit your needs!

12 |
13 | -------------------------------------------------------------------------------- /package/templates/controller.txt: -------------------------------------------------------------------------------- 1 | The format of the function is variadic, as it passes its values to the static Code helper class:

2 |
code( ... );
3 |

Depending on the values passed in, the function can output:

4 | 11 |

The selected option above calls:

12 |
code{{ $signature }};
13 | 14 |

Outputing - {{ $desc }} :

15 | 18 | 19 |

You can call the Code class directly if you prefer:

20 |
21 | Code::{{ $func }}{{ $signature }};
22 | 
23 | 24 |

Check its source code at:

25 |
vendor/davestewart/sketchpad/src/utils/Code.php
-------------------------------------------------------------------------------- /package/views/help/helpers/icon.blade.php: -------------------------------------------------------------------------------- 1 |

The format of the function is:

2 |
icon($name, $color = '')
3 | 4 |

You can output various combinations of icon and colour, including bootstrap color constants and booleans:

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @foreach ($data as $datum) 16 | 17 | 18 | 19 | 20 | @endforeach 21 | 22 |
IconCode
{!! $datum->icon !!}{{ $datum->code }}
23 | 24 |

This helper differs from all others in that it returns an HTML string, rather than immediately echoing it, which allows it to be used in other functions:

25 | 26 |
p(icon('bolt') . ' Email');
27 | 28 |

The table helper has built-in support for converting columns to icons.

-------------------------------------------------------------------------------- /package/views/help/helpers/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad ships with a variety of helper functions to make it as easy as possible to put content on-screen. 4 | 5 | Text helpers make it easy to output well-formatted HTML without building views: 6 | 7 | | Helper | Result 8 | | --------- | ------- 9 | | `p()` | Output paragraphs 10 | | `text()` | Output preformatted text 11 | | `code()` | Output syntax-highlighted code 12 | | `alert()` | Output Bootstrap alert elements (useful for reporting) 13 | 14 | Data helpers make it easy to inspect arrays, objects and classes without writing any code: 15 | 16 | | Helper | Result 17 | | --------- | ------- 18 | | `pr()` | `print_r` data 19 | | `vd()` | `var_dump` data 20 | | `dump()` | `dd()` but don't die 21 | | `json()` | Interactively inspect objects in JSON format 22 | | `ls()` | Output objects or classes as list of name / value pairs 23 | | `tb()` | Output an array of objects in table format 24 | 25 | 26 | Note that helper functions `echo` immediately. If you need the raw HTML, call the source `Html` method: 27 | 28 | ```php 29 | $html = Html::tb($data); 30 | ``` 31 | 32 | ### Source 33 | 34 | You can view the source for this section at: 35 | 36 | vendor/davestewart/sketchpad/package/views/help/helpers 37 | vendor/davestewart/sketchpad/src/help/docs/HelpersController.php 38 | 39 | You can view the source for the helper functions at: 40 | 41 | ```text 42 | vendor/davestewart/sketchpad/package/views/html/* 43 | ``` 44 | 45 | 46 | -------------------------------------------------------------------------------- /package/views/help/helpers/list.blade.php: -------------------------------------------------------------------------------- 1 |

The format of the function is:

2 |
ls($values, $options = '');
3 | 4 |

Valid options for lists are:

5 | {!! tb($opts, 'cols:100,400,300|html:example') !!} 6 | 7 |

This is the validation config array, formatted as a list:

8 | {!! ls($data, $options) !!} 9 | -------------------------------------------------------------------------------- /package/views/help/helpers/table.blade.php: -------------------------------------------------------------------------------- 1 |

The format of the function is:

2 |
tb($values, $options = '');
3 |

The table helper can render:

4 | 9 |

The options parameter passes formatting arguments, with a syntax similar to Laravel validation:

10 |
tb($data, '');
11 | 12 |

Within the options string, you can:

13 | 18 | 19 |

Valid options for tables are:

20 | 21 | {!! tb($data, 'cols:100,400,300|html:example') !!} 22 | 23 | 42 | 43 |

Experiment with updating the options parameter above, or click the values in the table above to update the example table below:

44 | 45 | {!! tb($preview, $options) !!} 46 | -------------------------------------------------------------------------------- /package/views/help/methods/comments.blade.php: -------------------------------------------------------------------------------- 1 |

This makes it easy to see what a method does before calling it:

2 |
 3 | /**
 4 |  * The first line of DocBlock comments are shown in the method list and the page heading
 5 |  *
 6 |  * This line will not be shown
 7 |  */
 8 | public function comments()
 9 | {
10 | 
11 | }
12 | 
13 | 14 |

For further customisation using DocBlocks, check out the section on tags.

-------------------------------------------------------------------------------- /package/views/help/methods/contents.blade.php: -------------------------------------------------------------------------------- 1 |

To show a contents page when you click on your controllers add an index() method and echo or return some content:

2 |
 3 | class SomeController extends Controller
 4 | {
 5 |     public function index()
 6 |     {
 7 |         md('path.to.index'); // example uses markdown, but you could just as easily use Blade
 8 |     }
 9 | }
10 | 11 |

If you want to cheat, just save a markdown file in the same folder as the controller:

12 | 13 |
14 | md(__DIR__ . '/some.md');
15 | 
16 | 17 |

See the markdown example for more info about the md() method.

18 | -------------------------------------------------------------------------------- /package/views/help/methods/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | This section covers the basics of writing, running and documenting code in Sketchpad. 4 | 5 | All executable code is run through controller methods. Methods in Sketchpad are like methods in Laravel, only better: 6 | 7 | - methods may return or simply echo output 8 | - method parameters show as UI controls 9 | - method parameters are automatically cast to the correct type 10 | - you can test, or run your code by adding a special flag 11 | - dependency injection works just the same 12 | - exceptions are shown without breaking the page 13 | - DocBlocks provide additional information for the front end 14 | 15 | ### Source 16 | 17 | You can view the source for this section at: 18 | 19 | vendor/davestewart/sketchpad/package/views/help/methods 20 | vendor/davestewart/sketchpad/src/help/docs/MethodsController.php 21 | -------------------------------------------------------------------------------- /package/views/help/methods/parameters.blade.php: -------------------------------------------------------------------------------- 1 |

The result of this call is:

2 |
Hello, {{ $name }}!
3 |

Optional parameters are exposed as editable front-end inputs:

4 |
 5 | public function parameters($name = 'World')
 6 | {
 7 |     echo "Hello, $name!";
 8 | }
 9 | 
10 |

Update the parameter to automatically call the method again

11 | -------------------------------------------------------------------------------- /package/views/help/methods/run.blade.php: -------------------------------------------------------------------------------- 1 |

This is a method; it was called at:

2 |
{{ $date }}
3 |

Run it again by clicking the Run button

4 | -------------------------------------------------------------------------------- /package/views/help/methods/test.blade.php: -------------------------------------------------------------------------------- 1 |

There are often occasions where you want to test code before running it.

2 | 3 |

Sketchpad allows you to set an additional boolean parameter $run which creates a special Test / Run toggle on the front end:

4 |
public function processFiles($run = false) { ... }
5 | 6 |

This allows you to preview output and only run additional code when happy with the results:

7 |
 8 | public function processFiles($run = false)
 9 | {
10 |     // show files
11 |     $files = $this->getFiles();
12 |     pr($files);
13 | 
14 |     // do something with files
15 |     if ($run)
16 |     {
17 |         foreach ($files as $file) { ... }
18 |     }
19 | }
20 | 
21 | 22 |

If you prefer, you can use parameter names $save or $update (which also change the button text):

23 |
public function addColumn($name = 1, $save = false) { ... }
24 | public function editUser($id = 1, $udpate = false) { ... }
25 | 26 |

Note that each time parameters are updated or the the method is called, the mode is reset to "Test".

27 | -------------------------------------------------------------------------------- /package/views/help/methods/typecasting.blade.php: -------------------------------------------------------------------------------- 1 | 2 |

Your method's parameter types (string, boolean, etc) determine the HTML input field types.

3 |
 4 | public function typeCasting($string = 'hello', $number = 1, $boolean = true, $mixed = null)
 5 | {
 6 |     // do something with parameters
 7 | }
 8 | 
9 | 10 |

They also enable Sketchpad to cast submitted values back to the expected type; no need for type-juggling in your methods:

11 | {!! vd($params) !!} 12 | 13 |

Should you need to override determined types, you can either type-hint your DocBocks:

14 |
15 | @param  string     $string     This is a text field
16 | @param  int        $number     This is a number field
17 | @param  boolean    $boolean    This is a checkbox
18 | @param  mixed      $mixed      This is a text field (but will be converted to the correct type)
19 | 
20 | 21 |

Or, use an additional @field tag to provide more information to the front end:

22 |
23 | @param  string     $string     Say hello in a chosen language
24 | @field  select     $string     options:hello,greetings,bonjour,hola,namaste
25 | 
26 | @param  int        $number     Pick a number in a range
27 | @field  number     $number     min:-10|max:10
28 | 
29 | 30 |

Click here for further information about @field.

-------------------------------------------------------------------------------- /package/views/help/methods/typehinting.blade.php: -------------------------------------------------------------------------------- 1 |

Like normal Laravel controllers, Sketchpad supports dependency injection via type hinting:

2 |
 3 | public function typeHinting(SketchpadConfig $config)
 4 | {
 5 |     pr($config->settings->data);
 6 | }
 7 | 
8 | 9 |

Here's the output:

10 | 11 | {!! pr($config->settings->data) !!} -------------------------------------------------------------------------------- /package/views/help/methods/variables.blade.php: -------------------------------------------------------------------------------- 1 |

View variables

2 |

Currently, only one variable is exposed to the view:

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
$routeThe base route the front end is accessed on (currently {{ $route }}) useful to access assets or routes.
12 |

This enables you to generate absolute links in your views if required:

13 |
<a href="@{{ $route }}some/link">Link</a>
14 |

Note that $route is available in Blade, Markdown, Vue views.

15 | 16 |

Path variables

17 |

Sketchpad namespaces your user views/ folder using the sketchpad:: identifier:

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
sketchpad::The path to your user view folder (currently {{ $views }})
27 | 28 |

This allows you to reference view files quickly and easily:

29 |
 30 | return view('sketchpad::user/view/path');
 31 | 
32 | 33 |
 34 | md('sketchpad::user/view/path');
 35 | 
36 | 37 |

Route variables

38 |

The Sketchpad service has various values pertaining to the called route:

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Sketchpad::$routeThe currently-called controller route (currently {{ $fullroute }})
Sketchpad::$paramsThe same parameters passed to the current method, but as an associative array
Sketchpad::$formAny form data passed from the front end. Use this in place of Request::all()
56 | 57 |

To access them, reference the class statically:

58 |
 59 | use davestewart\sketchpad\services\Sketchpad;
 60 | 
 61 | public function test()
 62 | {
 63 |     if (Sketchpad::$form) { ... }
 64 | }
 65 | 
66 | 67 | 68 | 69 |

Config variables

70 |

The SketchpadConfig class provides access to current configuration values:

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
$config->routeThe base route the front end is accessed on (currently {{ $route }})
$config->controllersThe configured list of controller folder paths
$config->assetsThe configured user assets folder
$config->viewsThe configured user views folder
$config->settingsA wrapper for the front end settings, automatically configured in storage/sketchpad/sketchpad.json
$config->adminThe admin settings, manually configured in storage/sketchpad/admin.json
99 | 100 |

To access them, resolve the instance via dependency injection or app():

101 |
102 | use davestewart\sketchpad\config\SketchpadConfig;
103 | 
104 | public function test(SketchpadConfig $config)
105 | {
106 |     // alternatively: $config = app(SketchpadConfig::class);
107 |     if ($config->admin->settings) { ... }
108 | }
109 | 
110 | -------------------------------------------------------------------------------- /package/views/help/output/blade.blade.php: -------------------------------------------------------------------------------- 1 |

Sketchpad namespaces your views/ folder to sketchpad:: so you can silo and load development views separately from your app:

2 |
3 | echo view('sketchpad::some-view'); // loads from ""
4 | 
5 |

Note that you can still load views from your application, using the normal syntax:

6 |
7 | echo view('some-view'); // loads from "resources/views/"
8 | 
9 | -------------------------------------------------------------------------------- /package/views/help/output/form.blade.php: -------------------------------------------------------------------------------- 1 |

Any form with an empty or missing "action" attribute will be intercepted and submitted by sketchpad:

2 |
<form action=""></form>
3 | 4 |

You then check the Sketchpad::$form variable in your methods and take action appropriately:

5 |
public function form()
 6 | {
 7 |     if (Sketchpad::$form)
 8 |     {
 9 |         foreach (Sketchpad::$form as $key => $value) { ... }
10 |     }
11 | }
12 | 13 |
14 | 15 |

Submit the following test form to see it loop back to the same URL:

16 | 17 |
18 | 19 | 20 |
21 | 22 | @if($form) 23 |

The form data is:

24 | {!! dump($form) !!} 25 | @else 26 |

Waiting for form data...

27 | @endif -------------------------------------------------------------------------------- /package/views/help/output/formatting.md: -------------------------------------------------------------------------------- 1 | Here are a selection of Sketchpad's default styles, all of which are editable or overridable via your user assets file. 2 | 3 | # This is an H1 4 | ## This is an H2 5 | ### This is an H3 6 | #### This is an H4 7 | ##### This is an H5 8 | 9 | 10 | This is a paragraph 11 | 12 | Here are **some** *styling* [examples](http://www.google.com) ... 13 | 14 | - this 15 | - is 16 | - a 17 | - list 18 | 19 | > Here is a block indent 20 | 21 | Here is some code: 22 | 23 | ```js 24 | // this is a comment 25 | for(var i = 0; i < 10 ; i++) 26 | { 27 | print(i); 28 | } 29 | ``` 30 | 31 | This is an unstyled table: 32 | 33 | | Value | Description | 34 | | ------ | ------ | 35 | | Foo | Lorem ipsum dolor sit amet, vix at adipiscing temporibus | 36 | | Bar | At eam decore utroque, vel cu alia persius | 37 | | Baz | Liberavisse euismod persequeris sit ei | 38 | 39 | This is a styled table: 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
ValueDescription
FooLorem ipsum dolor sit amet, vix at adipiscing temporibus
BarAt eam decore utroque, vel cu alia persius
BazLiberavisse euismod persequeris sit ei
63 | 64 | Here are some other pages with Markdown examples: 65 | 66 | - http://dillinger.io/ 67 | - http://showdownjs.github.io/demo/ 68 | - http://www.unexpected-vortices.com/sw/rippledoc/quick-markdown-example.html 69 | -------------------------------------------------------------------------------- /package/views/help/output/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad is designed to help you produce good-looking, meaningful and interactive content from the get-go. 4 | 5 | This includes: 6 | 7 | - formatting 8 | - loading views 9 | - rendering different view types 10 | - front-end interaction 11 | - transparent communication between the front and back-ends 12 | 13 | Note that the view helper functions `echo` immediately. If you need the raw HTML, call the source `Html` method: 14 | 15 | ```php 16 | $html = Html::md($path, $data); 17 | ``` 18 | 19 | ### Source 20 | 21 | You can view the source for this section at: 22 | 23 | vendor/davestewart/sketchpad/package/views/help/output 24 | vendor/davestewart/sketchpad/src/help/docs/OutputController.php 25 | 26 | -------------------------------------------------------------------------------- /package/views/help/output/links.blade.php: -------------------------------------------------------------------------------- 1 |

Relative paths run other methods:

2 | 6 | 7 |

Use the special sketchpad: protocol to link directly to methods:

8 | 11 | 12 |

Absolute paths (outside of sketchpad) run as normal:

13 | 16 | 17 |

External links run as normal:

18 | 22 | -------------------------------------------------------------------------------- /package/views/help/output/markdown.blade.php: -------------------------------------------------------------------------------- 1 |

The format of the function is:

2 |
md($path, $data = []);
3 | 4 |

Pass absolute paths or a sketchpad:: path:

5 |
md('sketchpad::path/to/view');
6 | 7 |

You can also inject data into Markdown views, in a manner similar to Blade:

8 |
// PHP
 9 | md('sketchpad::help/helpers/text', ['value' => 100]);
10 | 11 |
// Markdown
12 | The value is @{{value}} // note: no dollar sign!
13 | 14 |
// Result
15 | The value is 100
16 | 17 |

Note that the Sketchpad view route variable is passed along the same as Sketchpad views:

18 | 19 |
// Markdown
20 | Navigate to [settings](@{{route}}settings#paths)
21 | 22 |
// Result
23 | Navigate to <a href="/sketchpad/settings#paths">settings</a>
24 | 25 | -------------------------------------------------------------------------------- /package/views/help/output/pagination.blade.php: -------------------------------------------------------------------------------- 1 |

Sketchpad supports both implicit and explicit pagination.

2 | 3 |

Implicitly, Sketchpad intercepts all page links and passes along all GET values from the front to the back end.

4 |
 5 | public function pagination ($start = 1, $length = 10)
 6 | {
 7 |     // $_GET['page'] is passed if present in the URL
 8 | }
 9 | 
10 |

This allows Laravel's built-in pagination to work without any extra input:

11 | 12 | {!! tb($items) !!} 13 | {!! $paginator !!} 14 | 15 |

Explicitly, you can also add a page parameter to your methods, which will always be part of the URL, allowing you to set pagination manually:

16 |
17 | public function pagination ($start = 1, $length = 10, $page = 1)
18 | {
19 |     // manually set up pagination
20 | }
21 | 
-------------------------------------------------------------------------------- /package/views/help/output/vue-text.blade.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/views/help/output/vue-text.blade.php -------------------------------------------------------------------------------- /package/views/help/output/vue.vue: -------------------------------------------------------------------------------- 1 |
2 |

This is a sample Vue application; start typing to see it update:

3 |
4 | 5 | 6 | 7 |
8 |
9 | 10 | 16 | -------------------------------------------------------------------------------- /package/views/help/setup/assets.md: -------------------------------------------------------------------------------- 1 | Sketchpad provides a custom folder from which to load user assets: 2 | 3 | ```text 4 | {{assets}} 5 | ``` 6 | 7 | Three starter files were copied here during installation: 8 | 9 | ```text 10 | scripts.js 11 | styles.css 12 | favicon.ico 13 | ``` 14 | 15 | Feel free to modify, add or remove these files via your custom [head](head) file. 16 | 17 | You might also want to check out: 18 | 19 | - the settings [paths]({{route}}settings#paths) section to configure your asset path 20 | - the [formatting](../output/formatting) example to view Sketchpad's default styles 21 | - the [css tag](../tags/css) which can be used in conjunction with user assets to style the navigation element 22 | -------------------------------------------------------------------------------- /package/views/help/setup/controllers.blade.php: -------------------------------------------------------------------------------- 1 |

The purpose of Sketchpad is to provide a front-end wrappper around your controllers and methods.

2 | 3 |

It's really quite simple:

4 | 5 | 11 | 12 |

Sketchpad picks some default folders for you, which are configurable via the settings page:

13 | 14 | {!! tb($paths, 'cols:150,500|icon:enabled') !!} 15 | 16 |

You can rename, reorder, add, remove, enable and disable folders, with changes immediately reflected in the UI.

17 | 18 |

Note that abstract, methodless and private controllers will be ignored.

19 | -------------------------------------------------------------------------------- /package/views/help/setup/head.md: -------------------------------------------------------------------------------- 1 | Sketchpad allows you to inject custom head content via a custom [view](views) file: 2 | 3 | ```text 4 | {{head}} 5 | ``` 6 | 7 | 8 | The initial HTML is as follows: 9 | 10 | ```html 11 | {{html}} 12 | ``` 13 | Feel free to add or remove user assets, 3rd party libraries, add tracking, meta tags, etc. 14 | 15 | Note that the variables `assets` and `route` are available to use. -------------------------------------------------------------------------------- /package/views/help/setup/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | This section discusses setting up Sketchpad to boost your development productivity! 4 | 5 | It covers: 6 | 7 | - configuring controllers, views and custom assets 8 | - Live Reload and live coding 9 | - customising site name and top-level content 10 | - working with 3rd party libraries 11 | - preventing access to settings 12 | 13 | Most settings can be configured from the [Settings]({{route}}settings) page. 14 | -------------------------------------------------------------------------------- /package/views/help/setup/livereload.md: -------------------------------------------------------------------------------- 1 | To live-code and live-reload controllers, views and assets, install and run Sketchpad Reload. 2 | 3 | When developing, this allows you to work **almost exclusively** in your IDE, with Sketchpad responding and updating as you save files, rename elements, change, add or remove code: 4 | 5 | 6 | | Element | Event | Action 7 | | ------- | ------ | -------- 8 | | Method | Save | Console reloads 9 | | Parameters | Rename, change, add or delete | Console parameters update 10 | | Controller or method | Rename, reorder, add or delete | Navigation updates 11 | | Method | Runtime error | Console displays Laravel stack trace 12 | | Controller | Parse error | Navigation and Console display error indicator / message 13 | | Controller or method | Error correction | Navigation and Console update / reload automatically 14 | | Settings paths | Rename, change, add or delete | LiveReload updates and re-watches paths 15 | | User assets | Save | Styles or Script reloads 16 | | Custom home page | Save | Updates custom home page 17 | 18 | 19 | If using Sketchpad for **development** you should make installing Sketchpad Reload a priority: 20 | 21 | - If you've not yet using the package, visit the wiki for installation instructions 22 | - If you need to configure the package, visit the [settings]({{route}}settings#livereload) page. 23 | 24 | 25 | -------------------------------------------------------------------------------- /package/views/help/setup/middleware.md: -------------------------------------------------------------------------------- 1 | To assign middleware to Sketchpad, create a custom service provider file at: 2 | 3 | ``` 4 | app/Providers/SketchpadServiceProvider.php 5 | ``` 6 | 7 | The class should extend Sketchpad's own service provider, and needs only a single `middleware` property: 8 | 9 | ```php 10 | namespace app\Providers; 11 | 12 | class SketchpadServiceProvider extends \davestewart\sketchpad\SketchpadServiceProvider 13 | { 14 | protected $middleware = ['auth']; 15 | } 16 | ``` 17 | 18 | To ensure the new provider is loaded, update your `app.php` config: 19 | 20 | ``` 21 | // 3rd-party Service Providers... 22 | App\Providers\SketchpadServiceProvider::class 23 | ``` 24 | 25 | To assign middleware per controller, just use [Controller Middleware](https://laravel.com/docs/5.0/controllers#controller-middleware). -------------------------------------------------------------------------------- /package/views/help/setup/pages.md: -------------------------------------------------------------------------------- 1 | On installation, Sketchpad copies the following files to your [views](views) folder: 2 | 3 | ```text 4 | help.blade.php 5 | home.blade.php 6 | ``` 7 | They allow you to present different content depending on the install, so you could: 8 | 9 | - differentiate different Sketchpad installs 10 | - run Sketchpad as a standalone site with a custom home page 11 | - provide site-specific help regarding your custom content 12 | 13 | 14 | You can enable custom pages, or change the default folder, in the Site section of the [settings]({{route}}settings#site) page. 15 | -------------------------------------------------------------------------------- /package/views/help/setup/permissions.md: -------------------------------------------------------------------------------- 1 | Sketchpad has some basic functionality to set administrative privileges. 2 | 3 | The settings are stored in `admin.json` that ships with Sketchpad: 4 | 5 | storage/sketchpad/admin.json 6 | 7 | To prevent access to [settings]({{route}}settings) or [setup]({{route}}setup), edit this file then reload Sketchpad: 8 | 9 | ```json 10 | { 11 | "settings": true, 12 | "setup": true 13 | } 14 | ``` 15 | 16 | To set permissions per user, set values on the `SketchpadServiceProvider` in your `AppServiceProvider::boot()`: 17 | 18 | ```php 19 | SketchpadServiceProvider::set('admin.setup', false); 20 | SketchpadServiceProvider::set('admin.settings', false); 21 | ``` 22 | -------------------------------------------------------------------------------- /package/views/help/setup/settings.md: -------------------------------------------------------------------------------- 1 | For projects where you want control over the static content of a Sketchpad install, you can configure: 2 | 3 | - the **site name**, which will update the site and page titles 4 | - the [home page]({{route}}), using a custom Blade view 5 | - the [help page]({{route}}help), using a custom Blade view 6 | - whether to allow [search]({{route}}search) or [favourites]({{route}}favourites) pages 7 | 8 | Configure site setup on the [settings]({{route}}settings#site) page. 9 | 10 | If you choose custom home or help pages, edit their [files](pages) to suit your site. 11 | 12 | -------------------------------------------------------------------------------- /package/views/help/setup/views.md: -------------------------------------------------------------------------------- 1 | Sketchpad provides a custom folder to keep your Sketchpad views separate from your application views: 2 | 3 | ```text 4 | {{views}} 5 | ``` 6 | You can place the following Sketchpad-supported view types here: 7 | 8 | - [Blade](../output/blade) 9 | - [Markdown](../output/markdown) 10 | - [Vue](../output/vue) 11 | 12 | Load views from this folder using the [sketchpad::](../methods/variables) namespace: 13 | 14 | ``` 15 | echo view('sketchpad::somefile') 16 | ``` 17 | -------------------------------------------------------------------------------- /package/views/help/tags/css.blade.php: -------------------------------------------------------------------------------- 1 |

The current method item <li> has a suitably over-the-top class .fancy added to it:

2 |
@css fancy
3 |

It's styled (in part) with the following code in the user styles.css stylesheet (click here to disable it):

4 |
 5 | li.fancy{
 6 |     border:1px solid #333;
 7 |     background: repeating-linear-gradient(
 8 |         -45deg,
 9 |         #222,
10 |         #222 5px,
11 |         #333 5px,
12 |         #333 10px
13 |     );
14 | }
15 |

See the assets and formatting sections for more information.

16 | 23 | -------------------------------------------------------------------------------- /package/views/help/tags/field.blade.php: -------------------------------------------------------------------------------- 1 |

Overview

2 |

You can override Sketchpad's choice of input field with various HTML 5 element or input field types using the @field tag:

3 |
  4 | @field  select    $select     options:One=1,Two=2,Three=3
  5 | @field  number    $range      min:0|max:100|step:5
  6 | @field  date      $date
  7 | @field  color     $color
  8 | 
9 | 10 |

The output from the fields above can be seen here:

11 | {!! dump($params) !!} 12 | 13 |

Tag signature

14 | 15 |

The @field tag signature matches the @param tag signature, making it easy to pair the two up in your DocBlocks:

16 |
 17 | @param  type      $param      text
 18 | @field  type      $param      attributes
 19 | 
20 |

The value and type are derived from your method signature and @param tag, with any specific UI requirements drawn from the @field tag.

21 |

The format allows you to specify form element or input type, and optionally pass HTML attributes:

22 |
 23 | @field  date        $date
 24 | @field  color       $color
 25 | @field  number      $value      min:0|max:100|step:5
 26 | @field  select      $select     options:One=1,Two=2,Three=3
 27 | @field  datalist    $select     options:foo,bar,baz
 28 | 
29 | 30 |

The values are converted directly into HTML attributes:

31 |
 32 | <input type="number" value="" min="0" max="100" step="5">
 33 | 
34 | 35 |

Supported input types

36 |

The following element / input types are supported:

37 | 38 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
typeelementoutput
selectHTML select element 60 |
datalistHTML 5 datalist element 70 | 71 | 72 | 73 | 74 | 75 | 76 |
textHTML text input
numberHTML 5 number input
colorHTML 5 color input
dateHTML 5 date input
datetimeHTML 5 datetime-local input
timeHTML 5 time input
weekHTML 5 week input
monthHTML 5 month input
88 | 89 |

For select and dataset elements, ensure you pass an options value as outlined above.

90 |

Note that some HTML 5 input types (such as url and email) are not supported, as they are used primarily for validation, which is not yet supported in Sketchpad.

91 | 92 |

Attributes syntax

93 |

The attribute syntax is similar to the Laravel validation syntax, converting values to HTML attributes or options:

94 |
 95 | @field  type     $varname      value:0|values:1,2,3|options:One=1,Two=2,Three=3
 96 | 
97 | 98 |

Note the order of splitting / grouping operators for attributes:

99 | {!! tb($splits, 'html:operator,grouping,example|cols:100,400,200') !!} 100 | 101 | 102 |

Supported input attributes

103 |

As mentioned above, attribute name:value pairs are converted directly into HTML name="value" attributes, so you can pass whatever is relavent to the element's spec.

104 |

See the W3 Schools page for supported values for each element or input type.

105 | 106 |

Other options

107 |

If your input requirements are any more complex than this, consider using forms within your methods.

-------------------------------------------------------------------------------- /package/views/help/tags/iframe.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe 6 | 7 | 73 | 74 | 75 |
76 | 77 |

Use an @iframe to separate content...

78 |
79 | 80 |

Sketchpad loads content inline by default, but for pages already with styling or head content, this ain't gonna fly.

81 |

Consider using iframes when:

82 | 87 |

To automatically size any iframes to the size of their content (and so remove scrollbars) include the following script at the end of the loaded page:

88 |
<script src="{{ $route }}assets/js/iframe.js"></script>
89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /package/views/help/tags/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad uses tags to express additional functionality or formatting in the front end. 4 | 5 | Just add them to your methods' doc comments, and Sketchpad automatically implements that functionality: 6 | 7 | /** 8 | * I do something amazing 9 | * 10 | * @group Awesome methods 11 | * @icon address-book 12 | * @iframe 13 | */ 14 | public function someMethod () 15 | { 16 | // I'll load in an iframe 17 | } 18 | 19 | ### Source 20 | 21 | You can view the source for this section at: 22 | 23 | vendor/davestewart/sketchpad/package/views/help/tags 24 | vendor/davestewart/sketchpad/src/help/docs/TagsController.php 25 | -------------------------------------------------------------------------------- /package/views/help/tags/order.blade.php: -------------------------------------------------------------------------------- 1 |

The tag takes a single 1-based numeric integer:

2 |
@order 1
3 |

This is the intended position the controller should be ordered to; i.e. 1, 3, 10.

4 | -------------------------------------------------------------------------------- /package/views/help/tags/warn.blade.php: -------------------------------------------------------------------------------- 1 |

Hopefully the big red lozenge didn't put you off too much:

2 |
@warn
3 |

When the method is finally called, the deferred task, such as sending emails, will be run.

4 |

If you need to pass data to the deferred methods, your other options are:

5 | 10 | -------------------------------------------------------------------------------- /package/views/help/tools/cat.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | 7 | 23 | 24 | 38 | -------------------------------------------------------------------------------- /package/views/help/tools/folder.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 13 | 14 |

Path

15 | 23 | 24 |

Folders

25 | 33 | 34 |

Files

35 | 40 |
41 | -------------------------------------------------------------------------------- /package/views/help/tools/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad was designed for live-coding, but it's also a great place to store tools you'll run again and again. 4 | 5 | The tools contained here are just examples of the types of things you can build and run with sketchpad, using features such as: 6 | 7 | - Basic output [without views](../docs/output/noview) 8 | - HTML presentation using [paragraphs](../docs/helpers/paragraph) 9 | - Data presentation with [table](../docs/helpers/table) and [list](../docs/helpers/ls) 10 | - Data dumping with [dump](../docs/helpers/dump) and [json](../docs/helpers/json) 11 | - Custom parameter UI using the [field](../docs/tags/field) tag 12 | - Implicit [pagination](../docs/output/pagination) 13 | - Implicit [links](../docs/output/links) 14 | - [VueJS](../docs/output/vue) components 15 | - [Blade](../docs/output/blade) views 16 | - [Markdown](../docs/output/markdown) formatting 17 | - Sketchpad [view namespace](../docs/methods/variables) 18 | - View [$route](../docs/methods/variables) variable 19 | - [Contents](../docs/methods/content) page 20 | - Method [comments](../docs/methods/comments) 21 | - Method [tags](../docs/tags) 22 | 23 | ### Source 24 | 25 | You can view the source for this section at: 26 | 27 | vendor/davestewart/sketchpad/package/views/help/tools 28 | vendor/davestewart/sketchpad/src/help/demo/ToolsController.php 29 | -------------------------------------------------------------------------------- /package/views/help/tools/phpinfo.blade.php: -------------------------------------------------------------------------------- 1 | 22 |
{!! $contents !!}
23 | -------------------------------------------------------------------------------- /package/views/help/tools/routes.vue: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 |
Routes: {{ getCount(filter) }}
#{{ key }}
25 |
26 |
27 | 28 | 51 | 52 | -------------------------------------------------------------------------------- /package/views/html/list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @foreach($values as $key => $value) 15 | 16 | 17 | 18 | >{{ $obj ? print_r($value, true) : Html::getText($value) }} 19 | 20 | @endforeach 21 | 22 |
KeyValue
{{ $key }}
-------------------------------------------------------------------------------- /package/views/html/table.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | @if($caption) 4 | 5 | @endif 6 | 7 | 8 | @if($index) 9 | 10 | @endif 11 | @foreach($keys as $x => $key) 12 | 13 | @endforeach 14 | 15 | 16 | 17 | @foreach($values as $y => $obj) 18 | 21 | 22 | @if($index) 23 | 24 | @endif 25 | @foreach($keys as $x => $key) 26 | 34 | @if($isIcon) 35 | >{!! Html::icon($value) !!} 36 | @elseif($isHtml) 37 | >{!! Html::getText($value) !!} 38 | @else 39 | >{{ Html::getText($value) }} 40 | @endif 41 | @endforeach 42 | 43 | @endforeach 44 | 45 |
{{ $caption }}
#{{ $key }}
{{ $y + 1 }}
-------------------------------------------------------------------------------- /package/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ $title }} 7 | 8 | @include('sketchpad::sketchpad-head') 9 | 10 | @if ($livereload->host) 11 | 12 | @endif 13 | 14 | {!! $head !!} 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /package/views/no-help.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Help

3 |
4 |
5 |

Error

6 |

Custom help view {{ $help }} not found.

7 |

You can:

8 | 14 |
15 | -------------------------------------------------------------------------------- /package/views/no-home.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Home

3 |
4 |
5 |

Error

6 |

Custom home view {{ $home }} not found.

7 |

You can:

8 | 13 |
14 | -------------------------------------------------------------------------------- /package/views/no-setup.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sketchpad - Setup 7 | @include('sketchpad::sketchpad-head') 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 |
24 |
25 | 26 |
27 |

Setup

28 |
29 | 30 |
31 |
32 |

Setup is disabled

33 |

If you think this is a mistake, contact your System Administrator.

34 |

If you are the System Administrator see the wiki on how to enable setup.

35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /package/views/setup.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sketchpad - Setup 7 | @include('sketchpad::sketchpad-head') 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /package/views/sketchpad-head.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/SketchpadServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Paths::class); 43 | $this->app->singleton(SketchpadConfig::class); 44 | $this->app->singleton(Sketchpad::class); 45 | 46 | // install command 47 | $this->commands([ 48 | 'davestewart\sketchpad\commands\InstallCommand' 49 | ]); 50 | } 51 | 52 | /** 53 | * Bootstrap the application services. 54 | */ 55 | public function boot() 56 | { 57 | 58 | // ------------------------------------------------------------------------------------------------ 59 | // variables 60 | 61 | $root = realpath(__DIR__ . '/../') . '/'; 62 | $views = $root . 'package/views'; 63 | $config = app(SketchpadConfig::class); 64 | 65 | 66 | // ------------------------------------------------------------------------------------------------ 67 | // views 68 | 69 | $this->loadViewsFrom($views, 'sketchpad'); 70 | $this->loadViewsFrom(base_path($config->views), 'sketchpad'); 71 | view()->composer('*', function ($view) use ($config) { 72 | $view->with('route', $config->route); 73 | }); 74 | 75 | 76 | // ------------------------------------------------------------------------------------------------ 77 | // routes 78 | 79 | $parameters = 80 | [ 81 | 'namespace' => 'davestewart\sketchpad\controllers', 82 | 'middleware' => $this->middleware, 83 | ]; 84 | 85 | Route::group($parameters, function ($router) use ($config) 86 | { 87 | require (__DIR__ . '/routes.php'); 88 | }); 89 | 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Running Sketchpad installer...'); 41 | $installer->install(); 42 | $this->info('Sketchpad installer complete!'); 43 | } 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/config/InstallerSettings.php: -------------------------------------------------------------------------------- 1 | storage(); 33 | $this->path = $storage . 'install.json'; 34 | if( ! file_exists($storage) ) 35 | { 36 | mkdir($storage, 0777, true); 37 | } 38 | $this->load(); 39 | } 40 | 41 | 42 | // ----------------------------------------------------------------------------------------------------------------- 43 | // METHODS 44 | 45 | public function load() 46 | { 47 | if(file_exists($this->path)) 48 | { 49 | // load data 50 | $json = file_get_contents($this->path); 51 | $data = json_decode($json); 52 | 53 | // populate 54 | foreach($data as $key => $value) 55 | { 56 | if(property_exists($this, $key)) 57 | { 58 | $this->$key = $value; 59 | } 60 | } 61 | } 62 | } 63 | 64 | public function save($input) 65 | { 66 | // clean data 67 | //$input = array_map(function($value) { return trim($value, ' \\/'); }, $input); 68 | 69 | // settings for composer autoloader (hard-coded, a bit yukky) 70 | if(is_string($input['autoloader'])) 71 | { 72 | $input['autoloader'] = filter_var($input['autoloader'], FILTER_VALIDATE_BOOLEAN); 73 | } 74 | 75 | // save data 76 | $json = json_encode($input, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); 77 | return (bool) file_put_contents($this->path, $json); 78 | } 79 | 80 | 81 | } -------------------------------------------------------------------------------- /src/config/Paths.php: -------------------------------------------------------------------------------- 1 | _install = Paths::folder(base_path('vendor/davestewart/sketchpad/')); 25 | $this->_storage = storage_path('sketchpad/'); 26 | } 27 | 28 | 29 | // ------------------------------------------------------------------------------------------------ 30 | // paths 31 | 32 | /** 33 | * Returns the install path, i.e. vendor/davestewart/sketchpad/... 34 | * 35 | * @param string $path An optional path to append to the base path 36 | * @param bool $relative An optional flag to return the path relative to the base path 37 | * @return string The final path 38 | */ 39 | public function install($path = '', $relative = false) 40 | { 41 | return Paths::make($this->_install . $path, $relative); 42 | } 43 | 44 | /** 45 | * Returns the package path, i.e. vendor/davestewart/sketchpad/package/... 46 | * 47 | * @param string $path An optional path to append to the package path 48 | * @param bool $relative An optional flag to return the path relative to the base path 49 | * @return string The final path 50 | */ 51 | public function package($path = '', $relative = false) 52 | { 53 | return Paths::make($this->_install . 'package/' . $path, $relative); 54 | } 55 | 56 | /** 57 | * Returns the storage path, i.e. storage/sketchpad/... 58 | * 59 | * @param string $path An optional path to append to the storage path 60 | * @param bool $relative An optional flag to return the path relative to the base path 61 | * @return string The final path 62 | */ 63 | public function storage($path = '', $relative = false) 64 | { 65 | return Paths::make($this->_storage . $path, $relative); 66 | } 67 | 68 | 69 | // ------------------------------------------------------------------------------------------------ 70 | // utilities 71 | 72 | /** 73 | * Utility method to create a path; fixing and optionally making relative to the base path 74 | * 75 | * @param string $path The path to process 76 | * @param bool $relative An optional flag to return the path relative to the base path 77 | * @return string The final path 78 | */ 79 | public static function make($path, $relative = false) 80 | { 81 | return $relative 82 | ? Paths::relative($path) 83 | : Paths::fix($path); 84 | } 85 | 86 | /** 87 | * Utility method to make path relative to the base path 88 | * 89 | * @param string $path The path to process 90 | * @return string The final path 91 | */ 92 | public static function relative($path) 93 | { 94 | return Paths::fix(str_replace(base_path() . '/', '', $path)); 95 | } 96 | 97 | /** 98 | * Utility method to return a path with a trailing slash 99 | * 100 | * @param string $path The path to process 101 | * @param bool $relative An optional flag to return the path relative to the base path 102 | * @return string The final path 103 | */ 104 | public static function folder($path, $relative = false) 105 | { 106 | $path = Paths::make($path, $relative); 107 | return rtrim($path, '/') . '/'; 108 | } 109 | 110 | /** 111 | * Fixes-up paths: 112 | * 113 | * - replaces backslashes with forward slashes 114 | * - replaces multiple slashes with a single slash 115 | * 116 | * @param string $path The path to process 117 | * @return string The fixed path 118 | */ 119 | public static function fix($path) 120 | { 121 | $path = str_replace('\\', '/', $path); 122 | $path = preg_replace('%/+%', '/', $path); 123 | return $path; 124 | } 125 | 126 | } 127 | 128 | /** 129 | * Utility function to build file paths uniformly across OSes 130 | * 131 | * @params string ... One or many path segments 132 | * @return string A concatenated string of segments, with backslashes converted to / and double-slashes removed 133 | */ 134 | function path() 135 | { 136 | return Paths::fix(implode('', func_get_args())); 137 | } 138 | -------------------------------------------------------------------------------- /src/config/SketchpadConfig.php: -------------------------------------------------------------------------------- 1 | settings = new SketchpadSettings(); 67 | $this->loadSettings(); 68 | $this->loadAdmin(); 69 | } 70 | 71 | public function getView ($name) 72 | { 73 | $custom = rtrim($this->settings->get('site.views', ''), '/') . '/'; 74 | return Paths::fix($this->views . "/$custom/$name.blade.php"); 75 | } 76 | 77 | 78 | // ----------------------------------------------------------------------------------------------------------------- 79 | // accessors 80 | 81 | public function __get($name) 82 | { 83 | switch ($name) 84 | { 85 | case 'admin': 86 | return (object) array_merge([], (array) $this->admin); 87 | break; 88 | } 89 | return $this->$name; 90 | } 91 | 92 | 93 | // ----------------------------------------------------------------------------------------------------------------- 94 | // methods 95 | 96 | protected function loadSettings() 97 | { 98 | if ($this->settings->exists()) 99 | { 100 | $settings = $this->settings; 101 | 102 | // values 103 | $this->route = $settings->get('route'); 104 | $this->assets = $settings->get('paths.assets'); 105 | $this->views = $settings->get('paths.views'); 106 | $controllers = $settings->get('paths.controllers'); 107 | 108 | // ensure route is bounded by slashes to prevent concatenation issue later 109 | $this->route = '/' . ltrim('/' . trim($this->route, '/') . '/', '/'); 110 | 111 | // paths 112 | foreach($controllers as $obj) 113 | { 114 | if($obj['enabled']) 115 | { 116 | $this->controllers[$obj['name']] = rtrim($obj['path'], '/') . '/'; 117 | } 118 | } 119 | } 120 | } 121 | 122 | protected function loadAdmin () 123 | { 124 | // settings 125 | $admin = new JSON(storage_path('sketchpad/admin.json')); 126 | $data = $admin->data; 127 | 128 | // merge admin settings 129 | $admin = array_get(SketchpadServiceProvider::$settings, 'admin', []); 130 | $data = array_merge($data, $admin); 131 | 132 | // save 133 | $this->admin = (object) $data; 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /src/config/SketchpadSettings.php: -------------------------------------------------------------------------------- 1 | storage('settings.json')); 22 | } 23 | 24 | 25 | // ----------------------------------------------------------------------------------------------------------------- 26 | // methods 27 | 28 | public function save($data) 29 | { 30 | $this->data = $data; 31 | $this->create(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/controllers/ApiController.php: -------------------------------------------------------------------------------- 1 | sketchpad = $sketchpad; 41 | } 42 | 43 | 44 | // ------------------------------------------------------------------------------------------------ 45 | // endpoints 46 | 47 | /** 48 | * Run a controller method 49 | * 50 | * @param Request $request 51 | * @param string $path 52 | * @return \Illuminate\View\View|mixed|string 53 | */ 54 | public function run(Request $request, $path = '') 55 | { 56 | // data 57 | $data = $request->get('_data', []); 58 | if (is_string($data)) 59 | { 60 | $data = json_decode($data, JSON_OBJECT_AS_ARRAY); 61 | } 62 | 63 | // form 64 | $form = $request->get('_form', null); 65 | parse_str($form, $form); 66 | 67 | // run 68 | return $this->sketchpad->run($path, $data, $form); 69 | } 70 | 71 | /** 72 | * Returns the JSON for a single controller 73 | * 74 | * @param null $route 75 | * @return mixed 76 | * @internal param Request $request 77 | * @internal param $path 78 | */ 79 | public function load($route = null) 80 | { 81 | return response()->json($this->sketchpad->getController($route)); 82 | } 83 | 84 | /** 85 | * Loads custom page content 86 | * 87 | * @param string $name 88 | * @return string 89 | */ 90 | public function page($name) 91 | { 92 | return view("sketchpad::$name"); 93 | } 94 | 95 | /** 96 | * Loads or saves settings data 97 | * 98 | * @method POST 99 | * @method GET 100 | * @param Request $request 101 | * @param SketchpadConfig $config 102 | * @return SketchpadSettings 103 | */ 104 | public function settings(Request $request, SketchpadConfig $config) 105 | { 106 | if (!$config->admin->settings) 107 | { 108 | return response()->json($config->settings); 109 | } 110 | 111 | function textToArray ($text) 112 | { 113 | return array_values(array_filter(array_map('trim', explode("\n", trim($text))), 'strlen')); 114 | } 115 | 116 | if($request->isMethod('post')) 117 | { 118 | // convert data 119 | $data = json_decode($request->get('settings')); 120 | 121 | // trim values 122 | $data->livereload->paths = textToArray($data->livereload->paths); 123 | 124 | // save 125 | $config->settings->save((array) $data); 126 | } 127 | return response()->json($config->settings); 128 | } 129 | 130 | /** 131 | * Validates existence of a path 132 | * 133 | * @method GET 134 | * @param Request $request 135 | * @return array 136 | */ 137 | public function path(Request $request) 138 | { 139 | $relpath = $request->get('path'); 140 | $abspath = base_path($relpath); 141 | return [ 142 | 'relpath' => $relpath, 143 | 'abspath' => $abspath, 144 | 'exists' => file_exists($abspath) 145 | ]; 146 | } 147 | 148 | } 149 | 150 | require_once __DIR__ . '/../utils/helpers.php'; 151 | -------------------------------------------------------------------------------- /src/controllers/SetupController.php: -------------------------------------------------------------------------------- 1 | sketchpad = $sketchpad; 49 | $this->setup = new Setup(); 50 | 51 | // hacky middleware-less 403 52 | $admin = $config->admin; 53 | if (property_exists($admin, 'setup') && $admin->setup === false) 54 | { 55 | $this->setup->disabled(); 56 | } 57 | } 58 | 59 | 60 | // ------------------------------------------------------------------------------------------------ 61 | // setup methods 62 | 63 | public function index() 64 | { 65 | return $this->setup->view(); 66 | } 67 | 68 | /** 69 | * Handles form data from the setup controller 70 | * 71 | * @method POST 72 | * @param Request $request 73 | * @return array 74 | */ 75 | public function submit(Request $request) 76 | { 77 | $input = $request->all(); 78 | $state = $this->setup->saveData($input); 79 | $result = [ 80 | 'step' => 'config', 81 | 'success' => $state, 82 | 'message' => $state ? 'Config saved OK' : 'Unable to save config', 83 | 'data' => $input 84 | ]; 85 | if($state) 86 | { 87 | return $this->install(); 88 | } 89 | return $result; 90 | } 91 | 92 | /** 93 | * Attempts to install sketchpad 94 | * 95 | * @return array 96 | */ 97 | public function install() 98 | { 99 | Artisan::call('sketchpad:install'); 100 | } 101 | 102 | 103 | /** 104 | * Tests sketchpad was successfully installed 105 | * 106 | * @return array 107 | */ 108 | public function test() 109 | { 110 | $installer = new Installer(); 111 | $state = $installer->test(); 112 | $result = [ 113 | 'step' => 'install', 114 | 'success' => $state, 115 | 'message' => $state ? 'Installation OK' : 'Installation error', 116 | 'data' => $installer->logs 117 | ]; 118 | return $result; 119 | } 120 | 121 | } 122 | 123 | -------------------------------------------------------------------------------- /src/controllers/SketchpadController.php: -------------------------------------------------------------------------------- 1 | sketchpad = $sketchpad; 43 | } 44 | 45 | 46 | // ------------------------------------------------------------------------------------------------ 47 | // endpoints 48 | 49 | public function index(Request $request) 50 | { 51 | // not installed 52 | if(!$this->sketchpad->isInstalled()) 53 | { 54 | // run setup 55 | $setup = new Setup(); 56 | return $setup->index(); 57 | } 58 | 59 | // set up the router and rescan to get all data 60 | $config = $this->sketchpad->init(true)->config; 61 | 62 | // settings 63 | $settings = $config->settings; 64 | $assets = $config->route . 'assets/user/'; 65 | 66 | // user content 67 | $home = $config->getView('home'); 68 | $help = $config->getView('help'); 69 | $head = $config->getView('head'); 70 | $abshome = base_path($home); 71 | $abshelp = base_path($help); 72 | $abshead = base_path($head); 73 | 74 | // data 75 | $data = 76 | [ 77 | 'head' => '', 78 | 'route' => $config->route, 79 | 'assets' => $config->route . 'assets/', 80 | 'title' => $settings->get('site.name'), 81 | 'livereload' => (object) $settings->get('livereload'), 82 | 'settings' => $settings->data, 83 | 'admin' => $config->admin, 84 | 'home' => file_exists($abshome) ? view()->file($abshome, compact('home')) : view('sketchpad::no-home', compact('home')), 85 | 'help' => file_exists($abshelp) ? view()->file($abshelp, compact('help')) : view('sketchpad::no-help', compact('help')), 86 | 'data' => 87 | [ 88 | 'controllers' => $this->sketchpad->getController(), 89 | ] 90 | ]; 91 | 92 | // head 93 | if (file_exists($abshead)) 94 | { 95 | $data['head'] = preg_replace('/^/m', ' ', view()->file($abshead, compact('assets'))); 96 | } 97 | 98 | // view 99 | return view('sketchpad::index', $data); 100 | } 101 | 102 | public function asset(Paths $paths, $file) 103 | { 104 | return $this->getAsset($paths->package("assets/$file")); 105 | } 106 | 107 | public function userAsset(SketchpadConfig $config, $file) 108 | { 109 | return $this->getAsset(base_path(trim($config->assets, '/') . '/' . $file)); 110 | } 111 | 112 | 113 | // ------------------------------------------------------------------------------------------------ 114 | // helpers 115 | 116 | protected function getAsset($path) 117 | { 118 | // 404 119 | if(!file_exists($path)) 120 | { 121 | header("HTTP/1.0 404 Not Found"); 122 | exit; 123 | } 124 | 125 | // mimetype 126 | $info = pathinfo($path); 127 | $ext = $info['extension']; 128 | $mimes = 129 | [ 130 | 'js' => 'application/javascript', 131 | 'css' => 'text/css', 132 | 'gif' => 'image/gif', 133 | 'png' => 'image/png', 134 | 'woff' => 'application/font-woff', 135 | 'ttf' => 'application/x-font-ttf', 136 | 'ico' => 'image/x-icon', 137 | ]; 138 | $mime = isset($mimes[$ext]) 139 | ? $mimes[$ext] 140 | : 'application/octet-stream'; //'text/html'; 141 | 142 | // serve file 143 | $response = new BinaryFileResponse($path); 144 | $response->mustRevalidate(); 145 | $response->setCharset('UTF-8'); 146 | $response->headers->set('Content-type', $mime); 147 | $response->headers->set('Content-length', filesize($path)); 148 | return $response; 149 | 150 | } 151 | 152 | } 153 | 154 | require_once __DIR__ . '/../utils/helpers.php'; 155 | -------------------------------------------------------------------------------- /src/help/demo/ToolsController.php: -------------------------------------------------------------------------------- 1 | select('id', 'name', 'email', 'created_at') 35 | ->limit(1) 36 | ->paginate(15); 37 | 38 | if($users) 39 | { 40 | tb($users); 41 | echo $users; 42 | return; 43 | } 44 | p('Unable to show users'); 45 | 46 | } 47 | 48 | /** 49 | * Example tool with a Vue version of the `artisan route:list` command, plus filtering functionality 50 | */ 51 | public function viewRoutes() 52 | { 53 | // variables 54 | $routes = \Route::getRoutes(); 55 | $array = []; 56 | foreach ($routes as /** @var Route */ $route) 57 | { 58 | // properties 59 | $methods = method_exists($route, 'getMethods') ? $route->getMethods() : $route->methods; 60 | $uri = method_exists($route, 'getUri') ? $route->getUri() : $route->uri; 61 | $action = $route->getAction()['uses']; 62 | 63 | // data 64 | $array[] = 65 | [ 66 | 'methods' => implode('|', $methods), 67 | 'uri' => $uri, 68 | 'name' => $route->getName(), 69 | 'action' => $action instanceof \Closure ? 'Closure' : $action, 70 | 'middleware'=> implode(', ', $route->middleware()), 71 | ]; 72 | } 73 | 74 | vue('sketchpad::help/tools/routes', $array); 75 | } 76 | 77 | /** 78 | * Browse your local filesystem 79 | * 80 | * @param string $path 81 | * @return View|string 82 | */ 83 | public function browseFilesystem($path = '') 84 | { 85 | // helpers 86 | function getBreadcrumbs($base, $path) 87 | { 88 | $paths = ['/' => $base]; 89 | $path = trim($path, '/'); 90 | if ($path !== '') 91 | { 92 | $segments = explode('/', $path); 93 | $current = '/'; 94 | foreach($segments as $segment) 95 | { 96 | $current .= $segment . '/'; 97 | $paths[$current] = $segment; 98 | } 99 | } 100 | return $paths; 101 | } 102 | 103 | // paths 104 | $path = str_replace('../', '', $path); 105 | $path = '/' . trim(preg_replace('%[\\/]+%', '/', $path), '/'); 106 | $realpath = realpath(base_path($path)); 107 | $base = pathinfo(base_path())['basename']; 108 | 109 | // found 110 | if($realpath) 111 | { 112 | try 113 | { 114 | $objects = array_diff(scandir($realpath), ['.','..']); 115 | $breadcrumbs = getBreadcrumbs($base, $path); 116 | $folders = array_filter($objects, function($f) use ($realpath) { return is_dir($realpath . '/' . $f); }); 117 | $files = array_filter($objects, function($f) use ($realpath) { return is_file($realpath . '/' . $f); }); 118 | $path = rtrim($path, '/') . '/'; 119 | $parent = $path !== '/' ? preg_replace('%[^/]+/$%', '', $path) : '/'; 120 | 121 | return view('sketchpad::help/tools/folder', compact('parent', 'path', 'folders', 'files', 'breadcrumbs')); 122 | } 123 | catch(\Exception $e) 124 | { 125 | return "Unable to read folder '$path'"; 126 | } 127 | } 128 | 129 | // not found 130 | return "Path '$path' not found"; 131 | } 132 | 133 | /** 134 | * See what's in the session 135 | * 136 | * @group Environment 137 | */ 138 | public function viewSession() 139 | { 140 | ls(\Session::all(), true); 141 | } 142 | 143 | /** 144 | * Check your sketchpad settings 145 | */ 146 | public function sketchpadSettings(SketchpadConfig $config) 147 | { 148 | p('Settings:'); 149 | json($config->settings); 150 | p('Admin:'); 151 | json($config->admin); 152 | } 153 | 154 | /** 155 | * Output the result of `phpinfo()` 156 | * 157 | * Note the use of escaping into HTML to output the style tag 158 | * @param int $section The section to show 159 | * @field select $section options:All=-1,General=1,Credits=2,Configuration=4,Modules=8,Environment=16,Variables=32,License=64 160 | */ 161 | public function phpInfo($section = -1) 162 | { 163 | ob_start(); 164 | phpinfo($section); 165 | $contents = ob_get_contents(); 166 | ob_end_clean(); 167 | 168 | $contents = str_replace('
', '', $contents); 169 | $contents = preg_replace('/^[\s\S]+?body>/', '', $contents); 170 | $contents = preg_replace('/<\/body>[\s\S]+$/', '', $contents); 171 | 172 | return view('sketchpad::help/tools/phpinfo', compact('contents')); 173 | } 174 | 175 | 176 | /** 177 | * Just for fun! 178 | * 179 | * @group Other 180 | */ 181 | public function randomCat() 182 | { 183 | return view('sketchpad::help/tools/cat'); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/help/docs/MethodsController.php: -------------------------------------------------------------------------------- 1 | func_get_args()]); 83 | } 84 | 85 | /** 86 | * Sketchpad controllers can be type hinted 87 | */ 88 | public function typeHinting(SketchpadConfig $config) 89 | { 90 | return view('sketchpad::help/methods/typehinting', compact('config')); 91 | } 92 | 93 | /** 94 | * Sketchpad makes a few classes and variables available to you 95 | */ 96 | public function variables(SketchpadConfig $config) 97 | { 98 | $route = $config->route; 99 | $views = $config->views; 100 | $fullroute = Sketchpad::$route; 101 | return view('sketchpad::help/methods/variables', compact('route', 'fullroute', 'views')); 102 | } 103 | 104 | /** 105 | * The first line of DocBlock comments are shown in the method list and the page heading 106 | * 107 | * @group Organisation 108 | */ 109 | public function comments() 110 | { 111 | return view('sketchpad::help/methods/comments'); 112 | } 113 | 114 | /** 115 | * Add a contents page to controllers so you don't see empty space 116 | */ 117 | public function contents() 118 | { 119 | return view('sketchpad::help/methods/contents'); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/help/docs/OutputController.php: -------------------------------------------------------------------------------- 1 | echo statement in a method..."; 31 | } 32 | 33 | /** 34 | * **Return** any value and Sketchpad will attempt to serialize it 35 | * 36 | * @field select $type options:request,class,object,array,string,number,boolean 37 | */ 38 | public function values($type = 'array') 39 | { 40 | $data = array( 41 | 'foo' => 1, 42 | 'bar' => true, 43 | 'baz' => array(1, 2, 3), 44 | ); 45 | 46 | switch ($type) { 47 | case 'request': 48 | return new \Illuminate\Http\Request(array('type' => $type)); 49 | case 'class': 50 | return new Test($data); 51 | case 'object': 52 | return (object)$data; 53 | case 'array': 54 | return $data; 55 | case 'string': 56 | return 'hello'; 57 | case 'number': 58 | return 123; 59 | case 'boolean': 60 | return (bool)rand(0, 1); 61 | } 62 | } 63 | 64 | /** 65 | * Load Sketchpad-specific Blade files using the `sketchpad::` view namespace 66 | * 67 | * @group Views 68 | */ 69 | public function blade(SketchpadConfig $config) 70 | { 71 | return view('sketchpad::help/output/blade', ['views' => $config->views]); 72 | } 73 | 74 | /** 75 | * Load Markdown documents from your views folder, even pass data from PHP 76 | */ 77 | public function markdown() 78 | { 79 | echo view('sketchpad::help/output/markdown'); 80 | } 81 | 82 | /** 83 | * Load and run Vue apps from your views folder, even pass data from PHP 84 | */ 85 | public function vue($name = 'World') 86 | { 87 | ?> 88 |

The format of the function is:

89 |
vue($path, $data = []);
90 | 91 |

Pass absolute paths or a sketchpad:: path along with any data array like so:

92 |
vue('sketchpad::help/vue/form', ['name' => 'World']);
93 | 94 |

The data is injected into the view as a local variable $data, so just reference it in your view 95 | like so:

96 | '; 99 | vue('sketchpad::help/output/vue', ['name' => $name]); 100 | } 101 | 102 | /** 103 | * Sketchpad's is built around Bootstrap, with various tweaks to make it suitable for debugging and admin 104 | * 105 | * @group HTML 106 | */ 107 | public function formatting() 108 | { 109 | md('sketchpad::help/output/formatting'); 110 | } 111 | 112 | /** 113 | * Sketchpad intercepts normal HTML page links, so you can link to other methods 114 | */ 115 | public function links() 116 | { 117 | echo view('sketchpad::help/output/links'); 118 | } 119 | 120 | /** 121 | * Sketchpad makes using forms easy, by intercepting and `POST`ing action-less forms on your behalf 122 | */ 123 | public function forms() 124 | { 125 | echo view('sketchpad::help/output/form', ['form' => Sketchpad::$form]); 126 | } 127 | 128 | /** 129 | * Sketchpad is designed to work invisibly with pagination, by preserving URL variables between front and back end 130 | * 131 | * @param int $start 132 | * @param int $length 133 | */ 134 | public function pagination($start = 1, $length = 10) 135 | { 136 | // manually create paginator 137 | $data = array_map(function ($num) { 138 | return ['id' => $num, 'value' => "Item $num"]; 139 | }, range(1, 100)); 140 | $page = Paginator::resolveCurrentPage('page'); 141 | $path = Paginator::resolveCurrentPath(); 142 | $items = array_slice($data, abs($start - 1) + (($page - 1) * $length), $length); 143 | $paginator = new LengthAwarePaginator($items, count($data), $length, $page, [ 144 | 'path' => $path, 145 | 'pageName' => 'page', 146 | ]); 147 | 148 | // append existing parameters 149 | $paginator->appends(Sketchpad::$params); 150 | 151 | // output 152 | return view('sketchpad::help/output/pagination', compact('items', 'paginator')); 153 | 154 | } 155 | 156 | } 157 | 158 | class Test 159 | { 160 | protected $_data; 161 | public $data; 162 | 163 | public function __construct($data) 164 | { 165 | $this->_data = $data; 166 | $this->data = $data; 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/help/docs/SetupController.php: -------------------------------------------------------------------------------- 1 | settings->get('paths.controllers'); 27 | return view('sketchpad::help/setup/controllers', compact('paths')); 28 | } 29 | 30 | /** 31 | * Load views, including Markdown and Vue, from a custom folder 32 | */ 33 | public function views(SketchpadConfig $config) 34 | { 35 | $views = $config->settings->get('paths.views'); 36 | md('sketchpad::help/setup/views', ['views' => $views]); 37 | } 38 | 39 | /** 40 | * Load user scripts and styles to augment your Sketchpad development 41 | */ 42 | public function assets(SketchpadConfig $config) 43 | { 44 | $assets = $config->settings->get('paths.assets'); 45 | $views = $config->settings->get('paths.views'); 46 | md('sketchpad::help/setup/assets', compact('assets', 'views')); 47 | } 48 | 49 | /** 50 | * Set up and configure Sketchpad Reload to enable live-reloading and live-coding 51 | */ 52 | public function liveReload() 53 | { 54 | alert('LiveReload enabled', true); 55 | alert('LiveReload not running!', false); 56 | md('sketchpad::help/setup/livereload'); 57 | } 58 | 59 | /** 60 | * Customise various aspects of Sketchpad to make your setup more relevant to visitors 61 | * 62 | * @group Customisation 63 | */ 64 | public function settings(SketchpadConfig $config) 65 | { 66 | $views = $config->settings->get('paths.views'); 67 | md('sketchpad::help/setup/settings', compact('views')); 68 | } 69 | 70 | /** 71 | * Replace top-level Sketchpad pages with your own custom content 72 | */ 73 | public function pages(SketchpadConfig $config) 74 | { 75 | $views = $config->settings->get('paths.views'); 76 | md('sketchpad::help/setup/pages', compact('views')); 77 | } 78 | 79 | /** 80 | * Add 3rd-party libraries, tracking or other head content 81 | */ 82 | public function head(Paths $paths, SketchpadConfig $config) 83 | { 84 | $head = $config->getView('head'); 85 | $path = $paths->package('setup/views/head.blade.php'); 86 | $html = file_get_contents($path); 87 | md('sketchpad::help/setup/head', compact('head', 'html')); 88 | } 89 | 90 | /** 91 | * Use middleware with Sketchpad or Sketchpad routes 92 | * 93 | * @group Admin 94 | */ 95 | public function middleware() 96 | { 97 | md('sketchpad::help/setup/middleware'); 98 | } 99 | 100 | /** 101 | * Configure the admin file to provide guest usage and prevent changes to settings 102 | */ 103 | public function permissions() 104 | { 105 | md('sketchpad::help/setup/permissions'); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/help/docs/TagsController.php: -------------------------------------------------------------------------------- 1 | FontAwesome icon next to the method name 42 | * 43 | * @icon paper-plane 44 | */ 45 | public function icon() 46 | { 47 | p('Declare an icon name:'); 48 | text('@icon paper-plane'); 49 | p('Prefix with a color to colorize:'); 50 | text('@icon red paper-plane'); 51 | } 52 | 53 | /** 54 | * Apply custom css classes to the method list item 55 | * 56 | * @css fancy 57 | */ 58 | public function css() 59 | { 60 | return view('sketchpad::help/tags/css'); 61 | } 62 | 63 | /** 64 | * Adds a heading and divider before the method 65 | * 66 | * @group Organisation 67 | */ 68 | public function group() 69 | { 70 | p('Mark a method as the start of a group:'); 71 | text('@group I am a new group'); 72 | p('Add as many as you like:'); 73 | text('@group I am another new group!'); 74 | } 75 | 76 | /** 77 | * Marks a method as a favourite 78 | * 79 | * @favourite 80 | */ 81 | public function favourite() 82 | { 83 | p('Use UK or US spelling:'); 84 | text("@favourite\n@favorite"); 85 | } 86 | 87 | /** 88 | * Override controller order within a folder 89 | */ 90 | public function order() 91 | { 92 | echo view('sketchpad::help/tags/order'); 93 | } 94 | 95 | /** 96 | * Override basic HTML inputs with more complex HTML input types and attributes 97 | * 98 | * @group Input / Output 99 | * 100 | * @param int $select 101 | * @field select $select options:One=1,Two=2,Three=3 102 | * 103 | * @param int $range 104 | * @field number $range min:0|max:100|step:5 105 | * 106 | * @param string $date 107 | * @field date $date 108 | * 109 | * @param string $color 110 | * @field color $color 111 | * 112 | */ 113 | public function field($select = 1, $range = 0, $date = '2015-01-01', $color = 'ff0000') 114 | { 115 | $splits = 116 | [ 117 | [ 118 | 'operator' => '|', 119 | 'grouping' => 'attributes', 120 | 'example' => 'min:0|max:100', 121 | ], 122 | [ 123 | 'operator' => ':', 124 | 'grouping' => 'attribute name / attribute value', 125 | 'example' => 'step:5', 126 | ], 127 | [ 128 | 'operator' => ',', 129 | 'grouping' => 'options (select and datalist only)', 130 | 'example' => 'foo,bar,baz', 131 | ], 132 | [ 133 | 'operator' => '=', 134 | 'grouping' => 'option text / option value', 135 | 'example' => 'Yes=1', 136 | ], 137 | ]; 138 | 139 | $format = 'html:example|cols:100,400,200'; 140 | 141 | $params = Sketchpad::$params; 142 | 143 | return view('sketchpad::help/tags/field', compact('types', 'attributes', 'format', 'params', 'splits')); 144 | } 145 | 146 | /** 147 | * Append (rather than replace) results to the output panel 148 | * 149 | * @append 150 | */ 151 | public function append() 152 | { 153 | $value = str_pad(rand(1, 100), 3, ' ', STR_PAD_RIGHT); 154 | echo "
Some new value: $value     // re-run or re-save to update...
"; 155 | } 156 | 157 | /** 158 | * Load the method in an iframe, rather than rendering it to the page 159 | * 160 | * @iframe 161 | */ 162 | public function iframe() 163 | { 164 | return view('sketchpad::help/tags/iframe'); 165 | } 166 | 167 | /** 168 | * Defers the calling of a method until the "Run" button is clicked 169 | * 170 | * @group Behaviour 171 | * 172 | * @defer 173 | * @param int $foo 174 | */ 175 | public function defer($foo = 1) 176 | { 177 | p('Value $foo was set to ' .$foo. ' at ' . date('H:i:s')); 178 | text('@defer'); 179 | p('Look also at test mode for a more interactive way to test then run conditional code'); 180 | } 181 | 182 | /** 183 | * Shows a warning indicator next to the method name, highlights the comment in red, and defers calling of the method. 184 | * 185 | * @warn 186 | */ 187 | public function warn() 188 | { 189 | return view('sketchpad::help/tags/warn'); 190 | } 191 | 192 | /** 193 | * Dims the method name in the methods list 194 | * 195 | * @archived This method is no longer used 196 | */ 197 | public function archived() 198 | { 199 | p('Probably best to remove methods you no longer use, but if you want to keep them, you can mark them as archived:'); 200 | text('@archived'); 201 | } 202 | 203 | /** 204 | * Hides the controller or method from the Sketchpad front end 205 | * 206 | * @label private 207 | */ 208 | public function hidden() 209 | { 210 | p("Both controllers and methods can be marked as private, meaning they won't be added to Sketchpad's controller list:"); 211 | text('@private'); 212 | p('For situations where you want to hide what might otherwise be a private method (for example, a callback) simply add the private tag.'); 213 | } 214 | 215 | 216 | 217 | } -------------------------------------------------------------------------------- /src/objects/file/File.php: -------------------------------------------------------------------------------- 1 | name = array_pop($segments); 50 | $this->path = $path; 51 | $this->route = $route; 52 | } 53 | 54 | public function exists() 55 | { 56 | return file_exists($this->path); 57 | } 58 | 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/objects/file/Folder.php: -------------------------------------------------------------------------------- 1 | path = rtrim($this->path, '/') . '/'; 56 | $this->folders = []; 57 | $this->controllers = []; 58 | 59 | // get parent routes 60 | $this->setParents(); 61 | 62 | // get members 63 | $this->process($recursive); 64 | } 65 | 66 | /** 67 | * Process contained folders and controllers 68 | * 69 | * @param bool $recursive An optional flag to recursively get child folders 70 | */ 71 | public function process($recursive = false) 72 | { 73 | // reset 74 | $this->folders = []; 75 | $this->controllers = []; 76 | 77 | // variables 78 | $files = array_diff(scandir($this->path), ['.', '..']); 79 | 80 | // loop 81 | foreach ($files as $file) 82 | { 83 | $path = $this->path . $file; 84 | if(is_dir($path)) 85 | { 86 | $this->folders[] = new Folder($path, $recursive); 87 | } 88 | else if(is_file($path) && preg_match('/Controller.php$/', $path)) 89 | { 90 | $this->controllers[] = new Controller($path); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Build list of parent folders / routes 97 | */ 98 | protected function setParents() 99 | { 100 | // variables 101 | $segments = explode('/', trim($this->route, '/')); 102 | $path = ''; 103 | 104 | // build parent routes 105 | foreach($segments as $segment) 106 | { 107 | $path .= $segment . '/'; 108 | $this->parents[$segment] = $path; 109 | } 110 | 111 | // remove last parent 112 | array_pop($this->parents); 113 | } 114 | 115 | /** 116 | * Specify data which should be serialized to JSON 117 | */ 118 | public function jsonSerialize() 119 | { 120 | $data = (object) []; 121 | $data->type = 'folder'; 122 | $data->name = $this->name; 123 | $data->route = $this->route; 124 | $data->parents = $this->parents; 125 | $data->folders = $this->folders; 126 | $data->controllers = $this->controllers; 127 | return $data; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/objects/install/ClassTemplate.php: -------------------------------------------------------------------------------- 1 | trg, $matches); 32 | $this->classname = $matches[1]; 33 | 34 | // namespace 35 | $this->setNamespace(); 36 | } 37 | 38 | public function set($data) 39 | { 40 | $data = (array) $data; 41 | $text = $this->text; 42 | foreach($data as $key => $value) 43 | { 44 | switch ($key) 45 | { 46 | case 'namespace': 47 | $value = rtrim($value, '\\'); 48 | $text = preg_replace("%namespace\\s+.+?;%", "namespace $value;", $text); 49 | break; 50 | 51 | case 'extends': 52 | case 'class': 53 | $text = preg_replace("%$key\\s+\\[w\\]+%", "$key $value", $text); 54 | break; 55 | 56 | default: 57 | $text = str_replace("%$key%", $value, $text); 58 | } 59 | } 60 | $this->text = $text; 61 | return $this; 62 | } 63 | 64 | public function setNamespace() 65 | { 66 | if( ! $this->resolver ) 67 | { 68 | $this->resolver = new NamespaceResolver(); 69 | } 70 | $this->resolver->loadNamespaces(); 71 | $this->namespace = $this->resolver->getNamespace($this->trg, true); 72 | $data = 73 | [ 74 | 'namespace' => $this->namespace 75 | ]; 76 | $this->set($data); 77 | return $this; 78 | } 79 | 80 | public function getNamespace() 81 | { 82 | return $this->namespace; 83 | } 84 | 85 | public function loads() 86 | { 87 | $classpath = $this->namespace . '\\' . $this->classname; 88 | try 89 | { 90 | new ReflectionClass($classpath); 91 | return true; 92 | } 93 | catch(\Exception $e) 94 | { 95 | return false; 96 | } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/objects/install/Composer.php: -------------------------------------------------------------------------------- 1 | &1', $output); 28 | return trim(implode("\n", $output)); 29 | } 30 | 31 | public function hasPSR4Class($key) 32 | { 33 | $path = base_path('vendor/composer/autoload_psr4.php'); 34 | $data = require($path); 35 | return array_key_exists($key, $data); 36 | } 37 | } -------------------------------------------------------------------------------- /src/objects/install/Copier.php: -------------------------------------------------------------------------------- 1 | src = $this->makePath($src); 15 | $this->trg = $trg 16 | ? $this->getFileTargetPath($src, $this->makePath($trg)) 17 | : null; 18 | } 19 | 20 | public function create() 21 | { 22 | $this->copy($this->trg); 23 | } 24 | 25 | public function copy($trg) 26 | { 27 | if (is_dir($this->src)) 28 | { 29 | $this->fs->mirror($this->src, $trg); 30 | } 31 | else 32 | { 33 | $this->fs->copy($this->src, $trg); 34 | } 35 | } 36 | 37 | public function move($trg) 38 | { 39 | 40 | } 41 | 42 | public function exists() 43 | { 44 | return file_exists($this->trg); 45 | } 46 | 47 | public function remove() 48 | { 49 | $this->fs->remove($this->trg); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/objects/install/FilesystemObject.php: -------------------------------------------------------------------------------- 1 | fs = new Filesystem(); 16 | } 17 | 18 | /** 19 | * Utility function to return an absolute path from a relative or absolute path 20 | * 21 | * @param $path 22 | * @return string 23 | */ 24 | protected function makePath($path) 25 | { 26 | $path = $this->fs->isAbsolutePath($path) 27 | ? $path 28 | : base_path($path); 29 | return Paths::fix($path); 30 | } 31 | 32 | protected function isFilename($src) 33 | { 34 | return preg_match('%[^/]+\.\w+$%', $src); 35 | } 36 | 37 | protected function getFileTargetPath($src, $trg) 38 | { 39 | // data 40 | $parts = pathinfo($src); 41 | 42 | // if the src is a file, and the target is a folder, rename the target as a file 43 | if($this->isFilename($src) && ! $this->isFilename($trg)) 44 | { 45 | $trg = rtrim($trg, '/') . '/' . $parts['basename']; 46 | } 47 | 48 | // replace any pathinfo placeholders 49 | $trg = preg_replace_callback('/\{(dirname|basename|filename|extension)\}/', function($matches) use ($parts) 50 | { 51 | return $parts[$matches[1]]; 52 | }, $trg); 53 | 54 | return $trg; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/objects/install/Folder.php: -------------------------------------------------------------------------------- 1 | src = $this->makePath($src); 15 | } 16 | 17 | public function create() 18 | { 19 | $this->fs->mkdir($this->src); 20 | } 21 | 22 | public function exists() 23 | { 24 | return file_exists($this->src); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/objects/install/JSON.php: -------------------------------------------------------------------------------- 1 | data = []; 29 | $this->load(); 30 | } 31 | 32 | public function load() 33 | { 34 | if(file_exists($this->src)) 35 | { 36 | $text = file_get_contents($this->src); 37 | $this->data = json_decode($text, JSON_OBJECT_AS_ARRAY); 38 | $error = json_last_error(); 39 | 40 | if ($error !== JSON_ERROR_NONE) 41 | { 42 | switch ($error) { 43 | case JSON_ERROR_DEPTH: 44 | $error = 'Maximum stack depth exceeded'; 45 | break; 46 | case JSON_ERROR_STATE_MISMATCH: 47 | $error = 'Underflow or the modes mismatch'; 48 | break; 49 | case JSON_ERROR_CTRL_CHAR: 50 | $error = 'Unexpected control character found'; 51 | break; 52 | case JSON_ERROR_SYNTAX: 53 | $error = 'Malformed JSON'; 54 | break; 55 | case JSON_ERROR_UTF8: 56 | $error = 'Malformed UTF-8 characters, possibly incorrectly encoded'; 57 | break; 58 | default: 59 | $error = 'Unknown error'; 60 | break; 61 | } 62 | throw new \Exception("$error in '{$this->src}'"); 63 | } 64 | } 65 | 66 | return $this; 67 | } 68 | 69 | public function __get($name) 70 | { 71 | if($name === 'data') 72 | { 73 | return $this->data; 74 | } 75 | } 76 | 77 | public function set($key, $value = null) 78 | { 79 | if (is_string($key)) 80 | { 81 | array_set($this->data, $key, $value); 82 | } 83 | return $this; 84 | } 85 | 86 | public function get($key = '', $default = null) 87 | { 88 | return array_get($this->data, $key, $default); 89 | } 90 | 91 | public function has($key) 92 | { 93 | return array_has($this->data, $key); 94 | } 95 | 96 | public function create() 97 | { 98 | $text = json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 99 | $this->_save($this->trg, $text); 100 | } 101 | 102 | function jsonSerialize() 103 | { 104 | return $this->data; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/objects/install/NamespaceResolver.php: -------------------------------------------------------------------------------- 1 | setNamespaces($namespaces) 17 | : $this->loadNamespaces(); 18 | } 19 | 20 | /** 21 | * Loads namespaces from composer.json 22 | * 23 | * @return $this 24 | */ 25 | public function loadNamespaces() 26 | { 27 | $data = json_decode(file_get_contents(base_path('composer.json')), JSON_OBJECT_AS_ARRAY); 28 | $namespaces = array_get($data, 'autoload.psr-4'); 29 | $this->setNamespaces($namespaces); 30 | return $this; 31 | } 32 | 33 | /** 34 | * Sets the source namespace / path array file namespaces will be resolved from 35 | * 36 | * @param $namespaces 37 | * @return mixed 38 | */ 39 | public function setNamespaces($namespaces) 40 | { 41 | $this->namespaces = $namespaces; 42 | return $this; 43 | } 44 | 45 | /** 46 | * Attempts to get the namespace for a file 47 | * 48 | * @param string $file base-relative file path 49 | * @param bool $defaultToPath flag to use file path as namespace if namespace cannot be matched 50 | * @return null|string 51 | */ 52 | public function getNamespace($file, $defaultToPath = false) 53 | { 54 | // massage file path into a format compatible with PSR-4 entries 55 | $file = str_replace('\\', '/', $file); 56 | $base = str_replace('\\', '/', base_path()) . '/'; 57 | $file = str_replace($base, '', $file); 58 | 59 | // compare file path against existing entries 60 | foreach($this->namespaces as $ns => $path) 61 | { 62 | // convert paths to all use forward slashes 63 | $path = str_replace('\\', '/', $path); 64 | 65 | // if the file starts with the namespace path 66 | if(strpos($file, $path) === 0) 67 | { 68 | $file = substr($file, strlen($path)); 69 | $file = preg_replace('%/[^/]+$%', '', $file); 70 | $file = str_replace('/', '\\', $file); 71 | 72 | // defensive trim, in case any double slashes 73 | return trim($ns . $file, '\\'); 74 | } 75 | } 76 | 77 | // namespace could not be resolved 78 | $info = pathinfo($file); 79 | 80 | return $defaultToPath 81 | ? str_replace('/', '\\', $info['dirname']) 82 | : null; 83 | 84 | } 85 | } -------------------------------------------------------------------------------- /src/objects/install/Template.php: -------------------------------------------------------------------------------- 1 | load(); 24 | if($data) 25 | { 26 | $this->set($data); 27 | } 28 | } 29 | 30 | public function load() 31 | { 32 | $this->text = file_get_contents($this->src); 33 | return $this->text; 34 | } 35 | 36 | public function set($data) 37 | { 38 | $data = (array) $data; 39 | $text = $this->text; 40 | foreach($data as $key => $value) 41 | { 42 | $text = str_replace('%' . $key . '%', $value, $text); 43 | } 44 | $this->text = $text; 45 | return $this; 46 | } 47 | 48 | /** 49 | * Populates and writes the template to the target file 50 | * 51 | * @return bool 52 | */ 53 | public function create() 54 | { 55 | return (bool) $this->fs->dumpFile($this->trg, $this->text); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/objects/reflection/Comment.php: -------------------------------------------------------------------------------- 1 | intro = array_shift($blocks) ?: ''; 94 | $this->body = array_shift($blocks) ?: ''; 95 | } 96 | 97 | 98 | // ------------------------------------------------------------------------------------------------ 99 | // tags 100 | 101 | // match all tags, separate of paragraph text 102 | $this->params = []; 103 | $this->tags = []; 104 | 105 | preg_match_all('/@(\w+)(.*)/', $body, $matches); 106 | foreach ($matches[0] as $index => $match) 107 | { 108 | $name = $matches[1][$index]; 109 | $value = trim($matches[2][$index]); 110 | $tag = new Tag($name, $value); 111 | //dump([$name, $value, $tag]); 112 | if($name === 'param') 113 | { 114 | $this->params[$tag->name] = $tag; 115 | } 116 | if ($name === 'field') 117 | { 118 | $field = new Field($name, $value); 119 | $this->fields[$field->name] = $field; 120 | } 121 | else 122 | { 123 | if ($name === 'order') 124 | { 125 | $this->tags[$tag->name] = (int) $tag->text; 126 | } 127 | else 128 | { 129 | $this->tags[$tag->name] = $tag->text ? $tag->text : true; 130 | } 131 | } 132 | } 133 | //dump($this->tags); 134 | 135 | // fix favourites 136 | if(isset($this->tags['favorite'])) 137 | { 138 | unset($this->tags['favorite']); 139 | $this->tags['favourite'] = true; 140 | } 141 | } 142 | 143 | /** 144 | * Returns a parameter by name 145 | * 146 | * @param string $name 147 | * @return Tag|null 148 | */ 149 | public function getParam($name) 150 | { 151 | return isset($this->params[$name]) 152 | ? $this->params[$name] 153 | : null; 154 | } 155 | 156 | /** 157 | * Returns a tag by name 158 | * 159 | * @param string $name 160 | * @return Tag|null 161 | */ 162 | public function getTag($name) 163 | { 164 | return isset($this->tags[$name]) 165 | ? $this->tags[$name] 166 | : null; 167 | } 168 | 169 | /** 170 | * Returns a field by name 171 | * 172 | * @param string $name 173 | * @return Field|null 174 | */ 175 | public function getField($name) 176 | { 177 | return isset($this->fields[$name]) 178 | ? $this->fields[$name] 179 | : null; 180 | } 181 | 182 | public function jsonSerialize() 183 | { 184 | $data = (object) []; 185 | $data->intro = $this->intro; 186 | $data->body = $this->body; 187 | return $data; 188 | } 189 | 190 | } -------------------------------------------------------------------------------- /src/objects/reflection/Controller.php: -------------------------------------------------------------------------------- 1 | getFileName(); 60 | return new self($file); 61 | } 62 | 63 | public static function fromPath($path, $route = '', $process = true) 64 | { 65 | // check file exists 66 | if(!file_exists($path)) 67 | { 68 | return new ControllerError($path, $route, 'File does not exist'); 69 | } 70 | 71 | // class 72 | try 73 | { 74 | return new self($path, $route, $process); 75 | } 76 | 77 | // error 78 | catch(\Exception $error) 79 | { 80 | return new ControllerError($path, $route, 'Invalid class; check file name and class name'); 81 | } 82 | 83 | } 84 | 85 | public function __construct($path, $route = '', $process = true) 86 | { 87 | // parent 88 | parent::__construct($path, $route); 89 | 90 | // objects 91 | $class = getClassPath($path); 92 | $this->ref = new ReflectionClass($class); 93 | 94 | // properties 95 | $this->classname = $this->ref->getShortName(); 96 | $this->classpath = $this->ref->getName(); 97 | $this->label = $this->getLabel($this->classname); 98 | $this->comment = $this->getDocComment(); 99 | $this->methods = []; 100 | $this->order = (int) $this->comment->getTag('order'); 101 | 102 | // methods 103 | if($process) 104 | { 105 | $this->process(); 106 | } 107 | } 108 | 109 | public function process() 110 | { 111 | // variables 112 | $file = $this->ref->getFileName(); 113 | $methods = $this->ref->getMethods(ReflectionMethod::IS_PUBLIC); 114 | $arr = []; 115 | 116 | // get methods 117 | foreach ($methods as $m) 118 | { 119 | if($m->getFileName() === $file && $m->name !== '__construct') 120 | { 121 | $method = new Method($m, $this->route); 122 | if( ! isset($method->comment->tags['private']) ) 123 | { 124 | $arr[] = $method; 125 | } 126 | } 127 | } 128 | $this->methods = $arr; 129 | 130 | // return 131 | return $this; 132 | } 133 | 134 | public function getReference() 135 | { 136 | return new ControllerReference($this->route, $this->path, $this->classpath); 137 | } 138 | 139 | public function isValid() 140 | { 141 | return !$this->ref->isAbstract() 142 | && !$this->getTag('private') 143 | && count($this->methods) > 0; 144 | } 145 | 146 | public function toArray() 147 | { 148 | $data = 149 | [ 150 | 'type' => 'controller', 151 | 'class' => $this->classname, 152 | 'path' => str_replace(base_path() . '/', '', $this->path), 153 | 'name' => $this->name, 154 | 'route' => $this->route, 155 | 'folder' => preg_replace('%[^/]+$%', '', $this->route), 156 | 'order' => $this->order, 157 | 'label' => $this->label, 158 | 'comment' => $this->comment, 159 | 'methods' => $this->methods, 160 | ]; 161 | return $data; 162 | } 163 | 164 | function jsonSerialize() 165 | { 166 | return $this->toArray(); 167 | } 168 | 169 | } 170 | 171 | /** 172 | * Parses the class source to build a FQ class path 173 | * 174 | * @param $path 175 | * @return string 176 | */ 177 | function getClassPath($path) 178 | { 179 | $file = file_get_contents($path); 180 | preg_match('/namespace\s+([\w\\\\]+)/', $file, $namespace); 181 | preg_match('/class\s+(\w+)/', $file, $class); 182 | if($namespace && $class) 183 | { 184 | return $namespace[1] . '\\' . $class[1]; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/objects/reflection/ControllerError.php: -------------------------------------------------------------------------------- 1 | label = $label; 24 | $this->error = $error; 25 | } 26 | 27 | public function getReference() 28 | { 29 | return new ControllerErrorReference($this->route, $this->path, $this->error); 30 | } 31 | 32 | public function toArray() 33 | { 34 | $data = 35 | [ 36 | 'type' => 'controller', 37 | 'error' => $this->error, 38 | 'path' => str_replace(base_path() . '/', '', $this->path), 39 | 'folder' => preg_replace('%[^/]+$%', '', $this->route), 40 | 'route' => $this->route, 41 | 'label' => $this->label, 42 | 'methods' => [] 43 | ]; 44 | return $data; 45 | } 46 | 47 | function jsonSerialize() 48 | { 49 | return $this->toArray(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/objects/reflection/Field.php: -------------------------------------------------------------------------------- 1 | attrs = $this->text !== '' 24 | ? Options::create($this->text)->options 25 | : null; 26 | unset($this->text); 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/objects/reflection/Method.php: -------------------------------------------------------------------------------- 1 | ref = $method; 49 | $this->name = $method->name; 50 | $this->route = strtolower($route . '/' . $this->name); 51 | $this->label = $this->getLabel(); 52 | $this->comment = $this->getDocComment(); 53 | 54 | // params 55 | $params = $method->getParameters(); 56 | $this->params = []; 57 | foreach($params as /** @var ReflectionParameter */ $param) 58 | { 59 | if($param->isOptional()) 60 | { 61 | $name = $param->name; 62 | if ($name === 'run' || $name === 'update' || $name === 'save') 63 | { 64 | $this->runIf = $name; 65 | } 66 | else 67 | { 68 | $tag = $this->comment->getParam($param->name); 69 | $param = new Parameter($param, $tag); 70 | array_push($this->params, $param); 71 | } 72 | } 73 | } 74 | 75 | // fields 76 | if (!empty($this->comment->fields)) 77 | { 78 | foreach($this->params as $param) 79 | { 80 | $field = $this->comment->getField($param->name); 81 | if ($field) 82 | { 83 | $param->setField($field); 84 | } 85 | } 86 | } 87 | } 88 | 89 | public function toArray($simple = false) 90 | { 91 | // base 92 | $data = (object) []; 93 | $data->name = $this->name; 94 | $data->label = $this->label; 95 | $data->route = $this->route; 96 | $data->params = $this->params; 97 | $data->comment = $this->comment; 98 | $data->tags = $this->comment->tags; 99 | $data->runIf = $this->runIf; 100 | $data->runState = $this->runState; 101 | $data->error = 0; 102 | 103 | // return 104 | return $data; 105 | } 106 | 107 | function jsonSerialize() 108 | { 109 | return $this->toArray(); 110 | } 111 | 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/objects/reflection/Parameter.php: -------------------------------------------------------------------------------- 1 | name = $param->getName(); 75 | $this->optional = $param->isOptional(); 76 | $this->text = $tag ? $tag->text : ''; 77 | 78 | // value 79 | $value = $param->isOptional() && ! $param->isVariadic() 80 | ? $param->getDefaultValue() 81 | : $param->getName(); 82 | 83 | //$value = $value === null ? 'null' : $value; 84 | //$value = $value === false ? 'false' : $value; 85 | $this->value = $value; 86 | 87 | // type 88 | $this->type = $this->getType($param, $value, $tag ? $tag->type : null); 89 | } 90 | 91 | /** 92 | * Set the field value 93 | * 94 | * @param Field $field 95 | * @return $this 96 | */ 97 | public function setField ($field) 98 | { 99 | $this->field = $field; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Gets the parameter type 105 | * 106 | * @param ReflectionParameter $param 107 | * @param mixed $value 108 | * @param string $type 109 | * @return string 110 | */ 111 | protected function getType($param, $value, $type = null) 112 | { 113 | // attempt to get the type 114 | $type = method_exists($param, 'getType') 115 | ? $param->getType() 116 | : $type 117 | ? $type 118 | : gettype($value); 119 | 120 | // coerce type to something javascript will understand 121 | if($type == 'double' || $type == 'float' || $type == 'integer' || $type == 'int') 122 | { 123 | $type = 'number'; 124 | } 125 | else if($type == 'bool') 126 | { 127 | $type = 'boolean'; 128 | } 129 | else if($type == 'NULL') 130 | { 131 | $type = 'null'; 132 | } 133 | 134 | // return 135 | return $type; 136 | } 137 | 138 | /** 139 | * Specify data which should be serialized to JSON 140 | */ 141 | public function jsonSerialize() 142 | { 143 | // base values 144 | $values = (object) 145 | [ 146 | 'name' => $this->name, 147 | 'type' => $this->type, 148 | 'value' => $this->value, 149 | 'text' => $this->text, 150 | 'field' => null, 151 | 'attrs' => (object) [], 152 | ]; 153 | 154 | // override if field set 155 | if (isset($this->field)) 156 | { 157 | $values->field = $this->field->type; 158 | $values->attrs = (object) $this->field->attrs; 159 | 160 | } 161 | return $values; 162 | } 163 | 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/objects/reflection/Tag.php: -------------------------------------------------------------------------------- 1 | name = $name; 48 | $this->text = $text; 49 | 50 | // param 51 | if($name === 'param' || $name === 'field') 52 | { 53 | preg_match('/(\w+)\s+\$(\w+)(.*)/', $text, $matches); 54 | if($matches) 55 | { 56 | $this->type = $matches[1]; 57 | $this->name = $matches[2]; 58 | $this->text = trim($matches[3]); 59 | } 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/objects/route/CallReference.php: -------------------------------------------------------------------------------- 1 | abspath, $ref->route); 36 | $call = new self($controller->route, $controller->path, $controller->classpath); 37 | foreach($controller as $key => $value) 38 | { 39 | $call->$key = $value; 40 | } 41 | return $call; 42 | } 43 | 44 | public static function fromControllerRef(ControllerReference $controller) 45 | { 46 | $call = new self($controller->route, $controller->path, $controller->class); 47 | foreach($controller as $key => $value) 48 | { 49 | $call->$key = $value; 50 | } 51 | return $call; 52 | } 53 | 54 | /** 55 | * CallReference constructor 56 | * 57 | * @param string $route 58 | * @param string $path 59 | * @param string $class 60 | */ 61 | public function __construct($route, $path, $class = null) 62 | { 63 | parent::__construct('call', $route, $path); 64 | $this->class = $class; 65 | } 66 | 67 | 68 | // ------------------------------------------------------------------------------------------------ 69 | // methods 70 | 71 | /** 72 | * Determines the method to call 73 | * 74 | * @param $route 75 | * @return $this 76 | */ 77 | public function setMethod($route) 78 | { 79 | // variables 80 | $methodUri = trim(substr($route, strlen($this->route)), '/'); 81 | $segments = explode(',', $methodUri); 82 | 83 | // properties 84 | $this->method = array_shift($segments); 85 | 86 | // return 87 | return $this; 88 | } 89 | 90 | /** 91 | * Sets the calling parameters from the submitted front end data 92 | * 93 | * @param \StdClass[] $params 94 | * @return $this 95 | */ 96 | public function setParams($params) 97 | { 98 | $this->params = []; 99 | foreach ($params as $param) 100 | { 101 | // variables 102 | $name = $param['name']; 103 | $type = $param['type']; 104 | $value = $param['value']; 105 | 106 | // properties 107 | $this->params[$name] = $this->convert($type, $value); 108 | } 109 | 110 | // return 111 | return $this; 112 | } 113 | 114 | 115 | // ------------------------------------------------------------------------------------------------ 116 | // utilities 117 | 118 | /** 119 | * Utility used to convert values to the correct type 120 | * 121 | * @param string $type 122 | * @param mixed $value 123 | * @return mixed 124 | */ 125 | protected function convert ($type, $value) 126 | { 127 | switch ($type) 128 | { 129 | case 'string': 130 | return (string) $value; 131 | 132 | case 'number': 133 | return is_float($value) 134 | ? (float) $value 135 | : (int) $value; 136 | 137 | case 'boolean': 138 | return $value === true || $value === 'true' || $value === '1' || $value === 'on'; 139 | 140 | default: 141 | if (is_float($value)) 142 | { 143 | return $this->convert('number', $value); 144 | } 145 | if (is_numeric($value)) 146 | { 147 | return strpos($value, '.') !== FALSE 148 | ? (float) $value 149 | : (int) $value; 150 | } 151 | if ($value === 'true' || $value === 'false') 152 | { 153 | return $value === 'true'; 154 | } 155 | if ($value === '') 156 | { 157 | return null; 158 | } 159 | } 160 | return $value; 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /src/objects/route/ControllerErrorReference.php: -------------------------------------------------------------------------------- 1 | error = $error; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/objects/route/ControllerReference.php: -------------------------------------------------------------------------------- 1 | class = $class; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/objects/route/FolderReference.php: -------------------------------------------------------------------------------- 1 | type = $type; 24 | $this->route = $route; 25 | $this->path = str_replace(base_path() . '/', '', $abspath); 26 | 27 | } 28 | 29 | public function getName() 30 | { 31 | $segments = explode('/', trim($this->route, '/')); 32 | return array_pop($segments); 33 | } 34 | 35 | public function getDepth() 36 | { 37 | $segments = explode('/', trim($this->route, '/')); 38 | return count($segments); 39 | } 40 | 41 | public function exists() 42 | { 43 | return file_exists(base_path($this->path)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/objects/scanners/AbstractScanner.php: -------------------------------------------------------------------------------- 1 | isController($path) ) 28 | { 29 | $path = $this->scan(app_path()); 30 | } 31 | 32 | if($path) 33 | { 34 | $this->path = preg_replace('%/[^/\\\\]+$%', '/', $path); 35 | $this->namespace = $this->getNamespace($path); 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | protected function scan($path) 43 | { 44 | // variables 45 | $path = AbstractScanner::folderize($path); 46 | $files = array_diff(scandir($path), ['.', '..']); 47 | $folders = []; 48 | 49 | // loop, and process files first 50 | foreach ($files as $file) 51 | { 52 | $f = $path . $file; 53 | if(is_dir($f)) 54 | { 55 | $folders[] = $f; 56 | } 57 | else if($this->isController($f)) 58 | { 59 | return $f; 60 | } 61 | } 62 | 63 | // folders 64 | foreach($folders as $f) 65 | { 66 | $result = $this->scan($f); 67 | if($result) 68 | { 69 | return $result; 70 | } 71 | } 72 | } 73 | 74 | protected function getNamespace($path) 75 | { 76 | $file = file_get_contents($path); 77 | preg_match('/namespace\s+([\w\\\\]+)/', $file, $matches); 78 | if($matches) 79 | { 80 | return $matches[1]; 81 | } 82 | return null; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/objects/scanners/Scanner.php: -------------------------------------------------------------------------------- 1 | Controllers from the sketchpad/ folder downwards 16 | * - matching any called routes to said controllers 17 | * - creating any wildcard routes if required 18 | */ 19 | class Scanner extends AbstractScanner 20 | { 21 | 22 | use GetterTrait; 23 | 24 | // ----------------------------------------------------------------------------------------------------------------- 25 | // PROPERTIES 26 | 27 | /** 28 | * The base route for controllers 29 | * 30 | * @var string 31 | */ 32 | public $route; 33 | 34 | /** 35 | * An array of 'route' => RouteReference instances 36 | * 37 | * These are saved to the session and are used to quickly re-scan controllers later 38 | * 39 | * @var FolderReference[] 40 | */ 41 | public $routes; 42 | 43 | /** 44 | * An array of Controller instances 45 | * 46 | * These are used to build the controller JSON array 47 | * 48 | * @var FolderReference[] 49 | */ 50 | public $controllers; 51 | 52 | 53 | // ----------------------------------------------------------------------------------------------------------------- 54 | // INSTANTIATION 55 | 56 | /** 57 | * Router constructor. 58 | * 59 | * Sets up the Router with the values it needs determine or match routes 60 | * 61 | * @param string $path 62 | * @param string $route the base route for sketchpad routes 63 | */ 64 | public function __construct($path, $route = '') 65 | { 66 | // parameters 67 | $this->path = $path; 68 | $this->route = trim($route, '/') . '/'; 69 | 70 | // properties 71 | $this->routes = []; 72 | $this->controllers = []; 73 | } 74 | 75 | 76 | // ----------------------------------------------------------------------------------------------------------------- 77 | // SCANNING METHODS 78 | 79 | public function start() 80 | { 81 | $this->scan(); 82 | return $this; 83 | } 84 | 85 | 86 | // ----------------------------------------------------------------------------------------------------------------- 87 | // PROTECTED SCANNING METHODS 88 | 89 | /** 90 | * Recursively finds all controllers and folders 91 | * 92 | * Sets controllers and folders elements as they are found 93 | * 94 | * @param string $path The sketchpad controllers path-relative path to the folder, i.e. "foo/bar/" 95 | */ 96 | protected function scan($path = '') 97 | { 98 | // add folder 99 | $this->addFolder($path); 100 | 101 | // get elements 102 | $root = AbstractScanner::folderize($this->path . $path); 103 | $els = array_diff(scandir($root), ['.', '..']); 104 | 105 | // split elements 106 | $files = []; 107 | $folders = []; 108 | foreach ($els as $el) 109 | { 110 | is_dir($root . $el) 111 | ? array_push($folders, $el) 112 | : array_push($files, $el); 113 | } 114 | 115 | // get all controllers 116 | $controllers = []; 117 | $ordered = []; 118 | foreach ($files as $file) 119 | { 120 | $abspath = $root . $file; 121 | if($this->isController($abspath)) 122 | { 123 | $controller = $this->addController($abspath, $path); 124 | if ($controller) 125 | { 126 | property_exists($controller, 'order') && $controller->order !== 0 127 | ? array_push($ordered, $controller) 128 | : array_push($controllers, $controller); 129 | } 130 | } 131 | 132 | } 133 | 134 | // re-insert ordered controllers 135 | uasort($ordered, function ($a, $b) { return $a->order - $b->order; }); 136 | foreach($ordered as $c) 137 | { 138 | array_splice($controllers, $c->order - 1, 0, [$c]); 139 | } 140 | 141 | // add controllers 142 | $this->controllers = array_merge($this->controllers, array_values($controllers)); 143 | 144 | // folders 145 | foreach ($folders as $folder) 146 | { 147 | $relpath = $path . $folder; 148 | $this->scan($relpath . '/'); 149 | } 150 | } 151 | 152 | /** 153 | * Adds a folder to the internal routes array 154 | * 155 | * @param string $path The controller-root relative path to the folder 156 | */ 157 | protected function addFolder($path) 158 | { 159 | $route = rtrim($this->route . $path, '/'); 160 | $ref = new FolderReference($route, $this->path . $path); 161 | $this->addRoute($route, $ref); 162 | } 163 | 164 | /** 165 | * Adds a controller to the internal routes array 166 | * 167 | * @param string $abspath 168 | * @param string $route 169 | * @return Controller|\davestewart\sketchpad\objects\reflection\ControllerError 170 | */ 171 | protected function addController($abspath, $route) 172 | { 173 | // variables 174 | $name = pathinfo($abspath, PATHINFO_FILENAME); 175 | $segment = preg_replace('/Controller$/', '', $name); 176 | $route = strtolower($this->route . $route . $segment); 177 | 178 | // objects 179 | $instance = Controller::fromPath($abspath, $route); 180 | 181 | // add 182 | if($instance instanceof Controller) 183 | { 184 | // controller isn't abstract, has methods, isn't private 185 | if ($instance->isValid()) 186 | { 187 | $ref = $instance->getReference(); 188 | $this->addRoute($route, $ref); 189 | return $instance; 190 | } 191 | } 192 | 193 | else 194 | { 195 | $ref = $instance->getReference(); 196 | $this->addRoute($route, $ref); 197 | return $instance; 198 | } 199 | } 200 | 201 | /** 202 | * Adds a new RouteReference obejct 203 | * 204 | * @param string $route The URI route to be registered, i.e. "foo/bar/" 205 | * @param mixed $ref A PathReference instance 206 | */ 207 | protected function addRoute($route, $ref) 208 | { 209 | //$ref->route = $route; 210 | $this->routes[$route] = $ref; 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | route . 'api/run/{params?}', 'ApiController@run')->where('params', '.*'); 5 | Route::get ($config->route . 'api/load/{path?}', 'ApiController@load')->where('path', '.*'); 6 | 7 | // api - settings 8 | Route::get ($config->route . 'api/settings', 'ApiController@settings'); 9 | Route::post ($config->route . 'api/settings', 'ApiController@settings'); 10 | 11 | // api - tools 12 | Route::get ($config->route . 'api/path', 'ApiController@path'); 13 | Route::get ($config->route . 'api/page/{name}', 'ApiController@page'); 14 | 15 | // setup 16 | Route::get ($config->route . 'setup', 'SetupController@index'); 17 | Route::post ($config->route . 'setup/install', 'SetupController@submit'); 18 | Route::get ($config->route . 'setup/install', 'SetupController@install'); 19 | Route::get ($config->route . 'setup/test', 'SetupController@test'); 20 | 21 | // assets 22 | Route::get ($config->route . 'assets/user/{file}', 'SketchpadController@userAsset')->where(['file' => '.*']); 23 | Route::get ($config->route . 'assets/{file}', 'SketchpadController@asset')->where(['file' => '.*']); 24 | 25 | // sketchpad 26 | Route::get ($config->route . '{params?}', 'SketchpadController@index')->where('params', '.*'); 27 | -------------------------------------------------------------------------------- /src/services/Router.php: -------------------------------------------------------------------------------- 1 | Controllers from the sketchpad/ folder downwards 20 | * - matching any called routes to said controllers 21 | * - creating any wildcard routes if required 22 | * 23 | * @property Scanner $scanner 24 | */ 25 | class Router 26 | { 27 | 28 | use GetterTrait; 29 | 30 | // ----------------------------------------------------------------------------------------------------------------- 31 | // PROPERTIES 32 | 33 | /** 34 | * @var string[] 35 | */ 36 | protected $paths; 37 | 38 | /** 39 | * A cache of all routes so 40 | * 41 | * @var RouteReference[] 42 | */ 43 | protected $routes; 44 | 45 | /** 46 | * An array of full controller properties to pass to the front end 47 | * 48 | * @var Controller[] 49 | */ 50 | protected $controllers; 51 | 52 | 53 | // ----------------------------------------------------------------------------------------------------------------- 54 | // INSTANTIATION 55 | 56 | /** 57 | * Router constructor. 58 | * 59 | * Sets up the Router with the values it needs determine or match routes 60 | * 61 | * Parameters are all from the Sketchpad config file 62 | * 63 | * @param string[] $paths an array of paths 64 | */ 65 | public function __construct($paths) 66 | { 67 | $this->paths = (array) $paths; 68 | } 69 | 70 | 71 | // ----------------------------------------------------------------------------------------------------------------- 72 | // METHODS 73 | 74 | public function scan() 75 | { 76 | // reset 77 | $this->routes = []; 78 | $this->controllers = []; 79 | 80 | // scan 81 | foreach ($this->paths as $name => $path) 82 | { 83 | $root = strpos($path, '/') === 0 ? $path : base_path($path); 84 | if (file_exists($root)) 85 | { 86 | $scanner = new Scanner($root, $name); 87 | $scanner->start(); 88 | $this->routes = array_merge($this->routes, $scanner->routes); 89 | $this->controllers = array_merge($this->controllers, $scanner->controllers); 90 | } 91 | } 92 | 93 | // save routes 94 | Session::put('sketchpad.routes', $this->routes); 95 | 96 | // save method parameter types 97 | //ParamTypeManager::create()->saveAll($this->controllers); 98 | 99 | // return 100 | return $this; 101 | } 102 | 103 | /** 104 | * Reverse route lookup 105 | * 106 | * Compares a given route against routes determined when controllers were scanned 107 | * 108 | * Determines the controller, method and parameters to call if there is a match 109 | * 110 | * @param string $route 111 | * @param $params 112 | * @return ControllerReference|FolderReference|null 113 | */ 114 | public function getCall($route, array $params = []) 115 | { 116 | // variables 117 | $route = AbstractScanner::folderize($route); 118 | $routes = $this->getRoutes(); 119 | 120 | // debug 121 | // pr($route, $routes); 122 | 123 | // first, attempt to find an exact match 124 | // an exact match will either be a controller or a folder 125 | if(isset($routes[$route])) 126 | { 127 | return $routes[$route]; 128 | } 129 | 130 | // otherwise, the passed path will be at least a "controller/method" in which case, 131 | // we need to find the nearest partial match, then extract the component parts 132 | else 133 | { 134 | // variables 135 | /** @var CallReference $ref */ 136 | $ref = null; 137 | 138 | // loop over routes and grab matches 139 | // the last full match will be the one that we want 140 | foreach($routes as $key => $value) 141 | { 142 | //pr('KEY', $key, $value); 143 | if(strpos($route, $key) === 0) 144 | { 145 | $ref = $value; 146 | } 147 | } 148 | 149 | // debug 150 | //pr('REF', $ref); 151 | 152 | // if we got a matching route, update the ref with method and params 153 | if($ref instanceof ControllerReference) 154 | { 155 | return CallReference::fromControllerRef($ref) 156 | ->setMethod($route) 157 | ->setParams($params); 158 | } 159 | 160 | if($ref instanceof ControllerErrorReference) 161 | { 162 | return CallReference::fromRef($ref) 163 | ->setMethod($route) 164 | ->setParams($params); 165 | } 166 | 167 | } 168 | 169 | // return 170 | return null; 171 | } 172 | 173 | public function getRoutes() 174 | { 175 | // existing routes 176 | if($this->routes) 177 | { 178 | return $this->routes; 179 | } 180 | 181 | // saved routes 182 | $routes = Session::get('sketchpad.routes'); 183 | if($routes) 184 | { 185 | $this->routes = $routes; 186 | return $routes; 187 | } 188 | 189 | // scan routes 190 | $this->scan(); 191 | return $this->routes; 192 | } 193 | 194 | public function getFolders() 195 | { 196 | return array_filter($this->routes, function($ref){ return $ref instanceof FolderReference; }); 197 | } 198 | 199 | public function getControllers() 200 | { 201 | return $this->controllers; 202 | } 203 | 204 | public function getController($route) 205 | { 206 | // variables 207 | $route = strtolower($route); 208 | $routes = $this->getRoutes(); 209 | 210 | // filter 211 | foreach($routes as /** @var ControllerReference */$ref) 212 | { 213 | if(strtolower($ref->route) == $route) 214 | { 215 | // TODO not sure this is needed any more 216 | // check if the file still exists 217 | if(!$ref->exists()) 218 | { 219 | return (object) ["error" => "The file '{$ref->path}' does not exist"]; 220 | } 221 | 222 | // otherwise, get a reference 223 | $instance = Controller::fromPath(base_path($ref->path), $ref->route); 224 | 225 | // update session 226 | Session::put('sketchpad.routes.' . $ref->route, $instance->getReference()); 227 | 228 | // return 229 | return $instance; 230 | } 231 | } 232 | 233 | return (object) ["error" => "Invalid controller route '$route'"]; 234 | } 235 | 236 | } -------------------------------------------------------------------------------- /src/services/Setup.php: -------------------------------------------------------------------------------- 1 | path() !== $path 31 | ? redirect($path) 32 | : $this->view(); 33 | } 34 | 35 | 36 | // ------------------------------------------------------------------------------------------------ 37 | // setup 38 | 39 | /** 40 | * Shows the setup form view 41 | * 42 | * @return mixed 43 | */ 44 | public function view() 45 | { 46 | // default variables 47 | $finder = new Finder(); 48 | $finder->start(); 49 | 50 | // config 51 | $config = app(SketchpadConfig::class); 52 | 53 | // base name 54 | $basePath = Paths::fix(base_path('/')); 55 | $baseSegments = explode('/', rtrim($basePath, '/')); 56 | $baseName = array_pop($baseSegments) . '/'; 57 | 58 | // view path 59 | $viewPaths = Config::get('view.paths'); 60 | $viewPath = substr(Paths::fix($viewPaths[0]), strlen($basePath)); 61 | 62 | // variables 63 | $app = app(); 64 | $data = 65 | [ 66 | 'route' => $config->route, 67 | 'assets' => $config->route . 'assets/', 68 | 'settings' => 69 | [ 70 | 'route' => $config->route, 71 | 'basepath' => $basePath, 72 | 'basename' => $baseName, 73 | 'viewpath' => $viewPath, 74 | 'storagepath' => Paths::relative($config->settings->src), 75 | 'controllerpath' => trim(Paths::relative($finder->path), '/'), 76 | 'namespace' => method_exists($app, 'getNamespace') 77 | ? trim($app->getNamespace(), '\\') 78 | : 'App\\', 79 | 'namespaces' => (new JSON('composer.json'))->get('autoload.psr-4') 80 | ] 81 | ]; 82 | 83 | // return view 84 | return view('sketchpad::setup', $data); 85 | } 86 | 87 | public function disabled() 88 | { 89 | $config = app(SketchpadConfig::class); 90 | $data = 91 | [ 92 | 'route' => $config->route, 93 | 'assets' => '/' . $config->assets, 94 | 'path' => substr(storage_path('sketchpad/admin.json'), strlen(base_path()) + 1) 95 | ]; 96 | die(view('sketchpad::no-setup', $data)); 97 | } 98 | 99 | // ------------------------------------------------------------------------------------------------ 100 | // form 101 | 102 | public function saveData($input) 103 | { 104 | $settings = new InstallerSettings(); 105 | return $settings->save($input); 106 | } 107 | 108 | public function loadData() 109 | { 110 | $settings = new InstallerSettings(); 111 | return $settings; 112 | } 113 | 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/services/Sketchpad.php: -------------------------------------------------------------------------------- 1 | value pairs 47 | * 48 | * @var mixed[] 49 | */ 50 | public static $params; 51 | 52 | /** 53 | * Any submitted form data 54 | * 55 | * @var mixed[] 56 | */ 57 | public static $form; 58 | 59 | 60 | // ----------------------------------------------------------------------------------------------------------------- 61 | // INSTANTIATION 62 | 63 | public function __construct() 64 | { 65 | $this->config = app(SketchpadConfig::class); 66 | } 67 | 68 | 69 | // ----------------------------------------------------------------------------------------------------------------- 70 | // SETUP 71 | 72 | public function init($scan = false) 73 | { 74 | $this->router = new Router($this->config->controllers); 75 | if($scan) 76 | { 77 | //pr($this->router); 78 | $this->router->scan(); 79 | //pd($this->router); 80 | } 81 | return $this; 82 | } 83 | 84 | public function isInstalled () 85 | { 86 | return $this->config->settings->exists(); 87 | } 88 | 89 | 90 | // ------------------------------------------------------------------------------------------------ 91 | // GETTERS 92 | 93 | /** 94 | * Returns a sketchpad\objects\reflection\Controller that can be converted to JSON 95 | * 96 | * @param string $route The relative route to the controller 97 | * @return Controller The Controller 98 | */ 99 | public function getController($route = null) 100 | { 101 | $router = $this->init($route == null)->router; 102 | return $route 103 | ? $router->getController($route) 104 | : $router->getControllers(); 105 | } 106 | 107 | 108 | // ------------------------------------------------------------------------------------------------ 109 | // ROUTING METHODS 110 | 111 | /** 112 | * Initial function that works out the controller, method and parameters to call from the URI string 113 | * 114 | * @param string $route 115 | * @param array $params 116 | * @param array $form 117 | * @return mixed|string 118 | */ 119 | public function run($route = '', array $params, array $form = null) 120 | { 121 | // set up the router, but don't scan 122 | $this->init(); 123 | 124 | /** @var CallReference $ref */ 125 | $ref = $this->router->getCall($route, $params); 126 | 127 | //vd([$ref, $route, $params]); 128 | //exit; 129 | 130 | // controller has method 131 | if($ref instanceof CallReference) 132 | { 133 | // test controller / method exists 134 | try 135 | { 136 | new ReflectionMethod($ref->class, $ref->method); 137 | } 138 | catch(\Exception $e) 139 | { 140 | if($e instanceof \ReflectionException) 141 | { 142 | //$sketchpad = str_replace($this->config->route, '', $ref->route) . $ref->method . '/'; 143 | $this->abort($ref->route . '::' . $ref->method . '()', 'method'); 144 | } 145 | } 146 | 147 | // assign static properties 148 | Sketchpad::$route = $ref->route . '/' . $ref->method . '/'; 149 | Sketchpad::$params = $ref->params; 150 | Sketchpad::$form = empty($form) ? null : $form; 151 | 152 | // get controller response or content 153 | ob_start(); 154 | $response = $this->exec($ref->class, $ref->method, $ref->params); 155 | $content = ob_get_contents(); 156 | ob_end_clean(); 157 | 158 | // handle echoed output 159 | if ($content) 160 | { 161 | return $content; 162 | } 163 | 164 | // handle laravel view responses 165 | if ($response instanceof \Illuminate\Contracts\View\View) 166 | { 167 | return $response; 168 | } 169 | 170 | // handle laravel json responses 171 | if ($response instanceof \Illuminate\Http\JsonResponse) 172 | { 173 | return json($response->getData()); 174 | } 175 | 176 | // handle Arrrayable 177 | if ($response instanceof \Illuminate\Contracts\Support\Arrayable) 178 | { 179 | return json($response->toArray()); 180 | } 181 | 182 | // handle Jsonable 183 | if ($response instanceof \Illuminate\Contracts\Support\Jsonable) 184 | { 185 | return json(json_decode($response->toJson())); 186 | } 187 | 188 | // anything else send as JSON (classes, objects, arrays, numbers, strings, booleans) 189 | return json($response); 190 | } 191 | 192 | // if there's not a valid controller or method, it's a 404 193 | $this->abort($route, 'path'); 194 | return false; 195 | } 196 | 197 | 198 | // ------------------------------------------------------------------------------------------------ 199 | // UTILITIES 200 | 201 | /** 202 | * Calls a controller and methods, resolving any dependency injection 203 | * 204 | * @param string $controller The FQ name of the controller 205 | * @param string $method The name of the method 206 | * @param array|null $params An optional array of parameters 207 | * @return mixed The result of the call 208 | */ 209 | public function exec($controller, $method, $params = null) 210 | { 211 | $callable = "$controller@$method"; 212 | return $params 213 | ? App::call($callable, $this->getCallParams($controller, $method, $params)) 214 | : App::call($callable); 215 | } 216 | 217 | /** 218 | * Gets method parameters in the correct format to do an App:call() with dependency injection 219 | * 220 | * @param string $controller 221 | * @param string $method 222 | * @param string|array $params 223 | * @return array 224 | */ 225 | protected function getCallParams($controller, $method, $params) 226 | { 227 | // variables 228 | $values = is_array($params) ? $params : explode('/', $params); 229 | $ref = new ReflectionMethod($controller, $method); 230 | $params = $ref->getParameters(); 231 | $args = []; 232 | 233 | // map route segments to the method's parameters 234 | foreach ($params as /** @var \ReflectionParameter */ $param) 235 | { 236 | // parse signature [match, optional, type, name, default] 237 | preg_match('/<(required|optional)> (?:([\\\\a-z\d_]+) )?(?:\\$(\w+))(?: = (\S+))?/i', (string)$param, $matches); 238 | 239 | // assign untyped segments 240 | if ($matches[2] == null) 241 | { 242 | $args[$matches[3]] = array_shift($values); 243 | } 244 | } 245 | 246 | // append any remaining values 247 | return array_merge($args, $values); 248 | } 249 | 250 | /** 251 | * Aborts with a message 252 | * 253 | * @param string $uri The route that was called by the front end 254 | * @param string $type An object type, such as a controller, method, folder 255 | */ 256 | protected function abort($uri, $type = '') 257 | { 258 | App::abort(404, "The requested Sketchpad $type '$uri' does not exist"); 259 | } 260 | 261 | 262 | } -------------------------------------------------------------------------------- /src/traits/GetterTrait.php: -------------------------------------------------------------------------------- 1 | $name; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/traits/ReflectionTraits.php: -------------------------------------------------------------------------------- 1 | ref->getDocComment()); 51 | } 52 | 53 | /** 54 | * Determines the label for the element 55 | * 56 | * Returns a @label parameter, if available, otherwise, humanizes the element name 57 | * 58 | * @param null $default 59 | * @return null|string 60 | */ 61 | public function getLabel($default = null) 62 | { 63 | $label = $this->getTag('label'); 64 | if( ! $label ) 65 | { 66 | $label = $default ?: $this->ref->getName(); 67 | $label = preg_replace('/^(.+)Controller$/', '$1', $label); 68 | //$label = preg_replace('/_/', ' ', $label); 69 | //$label = preg_replace('/([a-z])([A-Z0-9])/', '$1 $2', $label); 70 | //$label = strtolower($label); 71 | } 72 | return $label; 73 | } 74 | 75 | /** 76 | * Gets the first available value of a tag 77 | * 78 | * @param string $name 79 | * @return string|null 80 | */ 81 | public function getTag($name) 82 | { 83 | $comment = $this->ref->getDocComment(); 84 | preg_match('/@' .$name. '\s+(.+)/', $comment, $matches); 85 | return $matches ? $matches[1] : null; 86 | } 87 | 88 | 89 | } -------------------------------------------------------------------------------- /src/traits/SaveFileTrait.php: -------------------------------------------------------------------------------- 1 | $text\n"; 20 | } 21 | 22 | /** 23 | * Output the contents of an entire file 24 | * 25 | * @param string $path 26 | * @param string $format 27 | * @return string 28 | */ 29 | public static function file($path, $format = '') 30 | { 31 | if ($format === '') 32 | { 33 | $format = self::getExtension($path); 34 | } 35 | $text = file_get_contents($path); 36 | return self::output($text, $format); 37 | } 38 | 39 | /** 40 | * Output a section of a single file 41 | * 42 | * @param string $path 43 | * @param int $start 44 | * @param int $end 45 | * @param bool $undent 46 | * @return string 47 | */ 48 | public static function section($path, $start = 0, $end = 0, $undent = false) 49 | { 50 | $format = self::getExtension($path); 51 | $text = file_get_contents($path); 52 | if ($start !== 0 || $end !== 0) 53 | { 54 | $text = self::lines($text, $start, $end); 55 | if ($undent) 56 | { 57 | $text = self::undent($text); 58 | } 59 | } 60 | return self::output($text, $format); 61 | } 62 | 63 | /** 64 | * Output the contents of a method 65 | * 66 | * @param string $class 67 | * @param string $method 68 | * @param bool $comment 69 | * @return string 70 | */ 71 | public static function method($class, $method, $comment = false) 72 | { 73 | $ref = new \ReflectionMethod($class, $method); 74 | $start = $ref->getStartLine(); 75 | $end = $ref->getEndLine(); 76 | $text = file_get_contents($ref->getFileName()); 77 | $text = self::lines($text, $start, $end); 78 | $text = self::undent($text); 79 | if ($comment) 80 | { 81 | $text = preg_replace('/^\s+\*/m', ' *', $ref->getDocComment()) . PHP_EOL . $text; 82 | } 83 | return self::output($text, 'php'); 84 | } 85 | 86 | /** 87 | * Output the contents of a class 88 | * 89 | * @param string $class 90 | * @param bool $comment 91 | * @return string 92 | */ 93 | public static function classfile($class, $comment = false) 94 | { 95 | $ref = new \ReflectionClass($class); 96 | $start = $ref->getStartLine(); 97 | $end = $ref->getEndLine(); 98 | $text = file_get_contents($ref->getFileName()); 99 | $text = self::lines($text, $start, $end); 100 | if ($comment) 101 | { 102 | $text = preg_replace('/^\s+\*/m', ' *', $ref->getDocComment()) . PHP_EOL . $text; 103 | } 104 | return self::output($text, 'php'); 105 | } 106 | 107 | /** 108 | * Return a range of lines from a block of text 109 | * 110 | * @param string $text 111 | * @param int $start 112 | * @param int $end 113 | * @return string 114 | */ 115 | public static function lines($text, $start, $end) 116 | { 117 | $lines = explode(PHP_EOL, $text); 118 | $code = implode(PHP_EOL, array_slice($lines, $start - 1, $end - $start + 1)); 119 | return $code; 120 | } 121 | 122 | /** 123 | * Return a formatted function signature 124 | * 125 | * @param array $args 126 | * @return string 127 | */ 128 | public static function signature($args = []) 129 | { 130 | $values = array_map(function ($v) { 131 | return is_string($v) 132 | ? "'$v'" 133 | : (is_bool($v) 134 | ? !! $v ? 'true' : 'false' 135 | : $v); 136 | }, $args); 137 | return '(' . implode(', ', $values) . ')'; 138 | } 139 | 140 | /** 141 | * Remove any indent from a block of text, based on the first line 142 | * 143 | * @param string $text 144 | * @return string 145 | */ 146 | public static function undent($text) 147 | { 148 | preg_match('/^\s+/', $text, $matches); 149 | list ($indent) = $matches; 150 | if ($indent) 151 | { 152 | $text = preg_replace("/^$indent/m", '', $text); 153 | } 154 | return $text; 155 | } 156 | 157 | /** 158 | * Gets the file extension of a path 159 | * 160 | * @param string $path 161 | * @return string 162 | */ 163 | protected static function getExtension ($path) 164 | { 165 | return strpos($path, '/') !== NULL 166 | ? substr($path, strrpos($path, '.') + 1) 167 | : ''; 168 | } 169 | } -------------------------------------------------------------------------------- /src/utils/Options.php: -------------------------------------------------------------------------------- 1 | options = $this->parse($str); 23 | } 24 | 25 | public static function create ($str = '') 26 | { 27 | return new Options($str); 28 | } 29 | 30 | 31 | // ------------------------------------------------------------------------------------------------ 32 | // methods 33 | 34 | public function has($name) 35 | { 36 | return array_key_exists($name, $this->options); 37 | } 38 | 39 | public function get($name, $default = null) 40 | { 41 | return array_key_exists($name, $this->options) 42 | ? $this->options[$name] 43 | : $default; 44 | } 45 | 46 | public function set($name, $value) 47 | { 48 | $this->options[$name] = $value; 49 | return $this; 50 | } 51 | 52 | public function __get($name) 53 | { 54 | return $this->get($name); 55 | } 56 | 57 | public function __set($name, $value) 58 | { 59 | $this->set($name, $value); 60 | } 61 | 62 | // ----------------------------------------------------------------------------------------------------------------- 63 | // utilties 64 | 65 | /** 66 | * Converts string options to a hash 67 | * 68 | * Operation: 69 | * 70 | * - splits options by | 71 | * - splits arguments by : 72 | * - splits argument members by , 73 | * - splits argument member names and values by = 74 | * 75 | * Example: 76 | * 77 | * index|html:path|pre:path,methods|values:One=1,Two=2,Three=3 78 | * 79 | * @param $input 80 | * @return array 81 | */ 82 | public function parse($input) 83 | { 84 | $output = []; 85 | $options = explode('|', $input); 86 | foreach($options as $option) 87 | { 88 | $name = $option; 89 | $value = 1; 90 | if(strpos($option, ':') !== false) 91 | { 92 | list($name, $value) = explode(':', $option, 2); 93 | } 94 | if (strstr($value, ',') !== false) 95 | { 96 | $values = explode(',', $value); 97 | if (strstr($value, '=') !== false) 98 | { 99 | $pairs = []; 100 | foreach($values as $value) 101 | { 102 | list($n, $v) = explode('=', $value, 2); 103 | $pairs[$n] = $v; 104 | } 105 | $values = $pairs; 106 | } 107 | $value = $values; 108 | } 109 | $output[$name] = $value; 110 | } 111 | return $output; 112 | } 113 | 114 | 115 | } -------------------------------------------------------------------------------- /src/utils/helpers.php: -------------------------------------------------------------------------------- 1 |