├── install-stubs ├── resources │ ├── js │ │ └── admin │ │ │ ├── index.js │ │ │ ├── app-components │ │ │ ├── Form │ │ │ │ ├── AppForm.js │ │ │ │ └── AppUpload.js │ │ │ ├── Listing │ │ │ │ └── AppListing.js │ │ │ └── bootstrap.js │ │ │ ├── admin.js │ │ │ └── bootstrap.js │ ├── sass │ │ └── admin │ │ │ ├── styles │ │ │ └── _index.scss │ │ │ ├── vendor │ │ │ └── _index.scss │ │ │ ├── admin.scss │ │ │ └── _variables.scss │ └── views │ │ ├── admin │ │ └── layout │ │ │ ├── profile-dropdown.blade.php │ │ │ ├── sidebar.blade.php │ │ │ └── logo.blade.php │ │ └── vendor │ │ └── mail │ │ └── html │ │ └── themes │ │ └── default.css ├── partial-webpack.mix.js ├── webpack.mix.js ├── database │ └── migrations │ │ └── create_wysiwyg_media_table.php └── config │ └── wysiwyg-media.php ├── .gitignore ├── resources ├── views │ └── admin │ │ ├── partials │ │ ├── main-bottom-scripts.blade.php │ │ ├── main-styles.blade.php │ │ ├── footer.blade.php │ │ ├── header.blade.php │ │ └── wysiwyg-svgs.blade.php │ │ ├── includes │ │ ├── avatar-uploader.blade.php │ │ └── media-uploader.blade.php │ │ └── layout │ │ ├── master.blade.php │ │ └── default.blade.php └── lang │ ├── sk │ └── admin.php │ └── en │ └── admin.php ├── tests ├── fixtures │ ├── resources │ │ └── views │ │ │ └── admin │ │ │ └── test │ │ │ └── index.blade.php │ └── public │ │ └── mix-manifest.json ├── Feature │ └── SimpleAdminTest.php └── TestCase.php ├── routes └── web.php ├── src ├── WysiwygMedia.php ├── Traits │ └── HasWysiwygMediaTrait.php ├── Http │ └── Controllers │ │ └── WysiwygMediaUploadController.php ├── AdminUIServiceProvider.php └── Console │ └── Commands │ └── AdminUIInstall.php ├── README.md ├── phpunit.xml ├── LICENSE └── composer.json /install-stubs/resources/js/admin/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .idea/ 4 | .DS_Store 5 | .phpunit.result.cache 6 | -------------------------------------------------------------------------------- /install-stubs/resources/sass/admin/styles/_index.scss: -------------------------------------------------------------------------------- 1 | // Write (or import) your styles here -------------------------------------------------------------------------------- /resources/views/admin/partials/main-bottom-scripts.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/admin/partials/main-styles.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /install-stubs/resources/sass/admin/vendor/_index.scss: -------------------------------------------------------------------------------- 1 | // Import here any vendor styles (e.g. from node_modules) 2 | @import "~craftable/scss/app"; -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/app-components/Form/AppForm.js: -------------------------------------------------------------------------------- 1 | import { BaseForm } from 'craftable'; 2 | 3 | export default { 4 | mixins: [BaseForm] 5 | }; -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/app-components/Listing/AppListing.js: -------------------------------------------------------------------------------- 1 | import { BaseListing } from 'craftable'; 2 | 3 | export default { 4 | mixins: [BaseListing] 5 | }; -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/app-components/Form/AppUpload.js: -------------------------------------------------------------------------------- 1 | import { BaseUpload } from 'craftable'; 2 | 3 | Vue.component('media-upload', { 4 | mixins: [BaseUpload] 5 | }); -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/app-components/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { Auth, TranslationForm, TranslationListing } from 'craftable'; 2 | import './Listing/AppListing'; 3 | import './Form/AppUpload'; -------------------------------------------------------------------------------- /install-stubs/resources/sass/admin/admin.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | @import "variables"; 3 | 4 | // Vendor 5 | @import "vendor/index"; 6 | 7 | // Your own, project specific styles 8 | @import "styles/index"; -------------------------------------------------------------------------------- /tests/fixtures/resources/views/admin/test/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('brackets/admin-ui::admin.layout.default') 2 | 3 | @section('body') 4 | 5 | Here should be some custom code :) 6 | 7 | @endsection -------------------------------------------------------------------------------- /tests/fixtures/public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/admin.js": "/js/admin.js", 3 | "/js/app.js": "/js/app.js", 4 | "/css/app.css": "/css/app.css", 5 | "/css/admin.css": "/css/admin.css" 6 | } -------------------------------------------------------------------------------- /install-stubs/partial-webpack.mix.js: -------------------------------------------------------------------------------- 1 | mix 2 | .js(["resources/js/admin/admin.js"], "public/js") 3 | .sass("resources/sass/admin/admin.scss", "public/css") 4 | .vue(); 5 | 6 | if (mix.inProduction()) { 7 | mix.version(); 8 | } -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(function () { 4 | Route::namespace('Brackets\AdminUI\Http\Controllers')->group(function () { 5 | Route::post('/admin/wysiwyg-media','WysiwygMediaUploadController@upload')->name('brackets/admin-ui::wysiwyg-upload'); 6 | }); 7 | }); -------------------------------------------------------------------------------- /install-stubs/resources/views/admin/layout/profile-dropdown.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/admin/partials/footer.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/WysiwygMedia.php: -------------------------------------------------------------------------------- 1 | file_path); 18 | }); 19 | } 20 | 21 | public function wysiwygable(): MorphTo 22 | { 23 | return $this->morphTo(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /install-stubs/webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix 15 | .js(["resources/js/admin/admin.js"], "public/js") 16 | .sass("resources/sass/admin/admin.scss", "public/css") 17 | .vue(); 18 | 19 | if (mix.inProduction()) { 20 | mix.version(); 21 | } -------------------------------------------------------------------------------- /tests/Feature/SimpleAdminTest.php: -------------------------------------------------------------------------------- 1 | visit('/admin/test/index'); 15 | 16 | $this->assertStringContainsString("Craftable - Craftable", $this->response->getContent()); 17 | 18 | $this->assertStringContainsString("Here should be some custom code :)", $this->response->getContent()); 19 | 20 | $this->assertStringContainsString("", $this->response->getContent()); 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /install-stubs/resources/views/admin/layout/sidebar.blade.php: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /resources/views/admin/includes/avatar-uploader.blade.php: -------------------------------------------------------------------------------- 1 | getMaxNumberOfFiles()) 6 | :max-number-of-files="{{ $mediaCollection->getMaxNumberOfFiles() }}" 7 | @endif 8 | @if($mediaCollection->getMaxFileSize()) 9 | :max-file-size-in-mb="{{ round(($mediaCollection->getMaxFileSize()/1024/1024), 2) }}" 10 | @endif 11 | @if($mediaCollection->getAcceptedFileTypes()) 12 | :accepted-file-types="'{{ implode(',', $mediaCollection->getAcceptedFileTypes()) }}'" 13 | @endif 14 | @if(isset($media) && $media->count() > 0) 15 | :uploaded-images="{{ $media->toJson() }}" 16 | @endif 17 | > -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Admin UI 2 | 3 | Admin UI is an administration template for Laravel 6 (LTS) & 7 & 8. It provides admin layout and basic UI elements to build up an administration area (CMS, e-shop, back-office, ...). 4 | 5 | Example of an administration interface built with this package: 6 | ![Craftable administration area example](https://docs.getcraftable.com/assets/posts-crud.png "Craftable administration area example") 7 | 8 | This packages is part of [Craftable](https://github.com/BRACKETS-by-TRIAD/craftable) (`brackets/craftable`) - an administration starter kit for Laravel 6 (LTS) & 7 & 8. You should definitely have a look :) 9 | 10 | You can find full documentation at https://docs.getcraftable.com/#/admin-ui 11 | 12 | ## Issues 13 | Where do I report issues? 14 | If something is not working as expected, please open an issue in the main repository https://github.com/BRACKETS-by-TRIAD/craftable. 15 | -------------------------------------------------------------------------------- /install-stubs/database/migrations/create_wysiwyg_media_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('file_path'); 19 | $table->unsignedInteger('wysiwygable_id')->nullable()->index(); 20 | $table->string('wysiwygable_type')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('wysiwyg_media'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/admin.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | 3 | import 'vue-multiselect/dist/vue-multiselect.min.css'; 4 | import flatPickr from 'vue-flatpickr-component'; 5 | import VueQuillEditor from 'vue-quill-editor'; 6 | import Notifications from 'vue-notification'; 7 | import Multiselect from 'vue-multiselect'; 8 | import VeeValidate from 'vee-validate'; 9 | import 'flatpickr/dist/flatpickr.css'; 10 | import VueCookie from 'vue-cookie'; 11 | import { Admin } from 'craftable'; 12 | import VModal from 'vue-js-modal' 13 | import Vue from 'vue'; 14 | 15 | import './app-components/bootstrap'; 16 | import './index'; 17 | 18 | import 'craftable/dist/ui'; 19 | 20 | Vue.component('multiselect', Multiselect); 21 | Vue.use(VeeValidate, {strict: true}); 22 | Vue.component('datetime', flatPickr); 23 | Vue.use(VModal, { dialog: true, dynamic: true, injectModalsContainer: true }); 24 | Vue.use(VueQuillEditor); 25 | Vue.use(Notifications); 26 | Vue.use(VueCookie); 27 | 28 | new Vue({ 29 | mixins: [Admin], 30 | }); -------------------------------------------------------------------------------- /resources/views/admin/layout/master.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{-- TODO translatable suffix --}} 13 | @yield('title', 'Craftable') - {{ trans('brackets/admin-ui::admin.page_title_suffix') }} 14 | 15 | @include('brackets/admin-ui::admin.partials.main-styles') 16 | 17 | @yield('styles') 18 | 19 | 20 | 21 | 22 | @yield('header') 23 | 24 | @yield('content') 25 | 26 | @yield('footer') 27 | 28 | @include('brackets/admin-ui::admin.partials.wysiwyg-svgs') 29 | @include('brackets/admin-ui::admin.partials.main-bottom-scripts') 30 | @yield('bottom-scripts') 31 | 32 | 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./app 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /install-stubs/resources/js/admin/bootstrap.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import _ from 'lodash'; 3 | import Vue from 'vue'; 4 | import jQuery from 'jquery'; 5 | import moment from 'moment'; 6 | 7 | window.$ = window.jQuery = jQuery; 8 | window.Vue = Vue; 9 | window._ = _; 10 | window.axios = axios; 11 | window.moment = moment; 12 | 13 | 14 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 15 | 16 | /** 17 | * Next we will register the CSRF Token as a common header with Axios so that 18 | * all outgoing HTTP requests automatically have it attached. This is just 19 | * a simple convenience so we don't have to attach every token manually. 20 | */ 21 | 22 | let token = document.head.querySelector('meta[name="csrf-token"]'); 23 | 24 | if (token) { 25 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 26 | $.ajaxSetup({headers: {'X-CSRF-TOKEN': token.content}}); 27 | } else { 28 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 29 | } 30 | -------------------------------------------------------------------------------- /resources/views/admin/layout/default.blade.php: -------------------------------------------------------------------------------- 1 | @extends('brackets/admin-ui::admin.layout.master') 2 | 3 | @section('header') 4 | @include('brackets/admin-ui::admin.partials.header') 5 | @endsection 6 | 7 | @section('content') 8 | 9 |
10 | 11 | @if(View::exists('admin.layout.sidebar')) 12 | @include('admin.layout.sidebar') 13 | @endif 14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 | 25 | @yield('body') 26 |
27 |
28 |
29 | @endsection 30 | 31 | @section('footer') 32 | @include('brackets/admin-ui::admin.partials.footer') 33 | @endsection 34 | 35 | @section('bottom-scripts') 36 | @parent 37 | @endsection -------------------------------------------------------------------------------- /src/Traits/HasWysiwygMediaTrait.php: -------------------------------------------------------------------------------- 1 | filter(function($wysiwygId){ 13 | return is_int($wysiwygId); 14 | }); 15 | if($wysiwygMediaIds->isNotEmpty()) { 16 | WysiwygMedia::whereIn('id', $wysiwygMediaIds)->get()->each(function($item) use ($model) { 17 | $model->wysiwygMedia()->save($item); 18 | }); 19 | } 20 | }); 21 | 22 | static::deleted(function($model) { 23 | $model->wysiwygMedia->each(function($item){ 24 | $item->delete(); 25 | }); 26 | }); 27 | } 28 | 29 | public function wysiwygMedia() 30 | { 31 | return $this->morphMany('Brackets\AdminUI\WysiwygMedia', 'wysiwygable'); 32 | } 33 | } -------------------------------------------------------------------------------- /install-stubs/config/wysiwyg-media.php: -------------------------------------------------------------------------------- 1 | env('WYSIWYG_MAXIMUM_IMAGE_WIDTH', 1000), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Media folder 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the folder in which the 24 | | wysiwyg media should be saved. Folder must 25 | | be inside "public" folder. 26 | | 27 | */ 28 | 29 | 'media_folder' => env('WYSIWYG_MEDIA_FOLDER', 'uploads'), 30 | 31 | ]; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BRACKETS by TRIAD s.r.o. 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 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | app['router']->get('/admin/test/index', function(){ 23 | return view('admin.test.index'); 24 | }); 25 | 26 | File::copyDirectory(__DIR__.'/fixtures/public', public_path()); 27 | File::copyDirectory(__DIR__.'/fixtures/resources/views', resource_path('views')); 28 | } 29 | 30 | /** 31 | * @param \Illuminate\Foundation\Application $app 32 | * 33 | * @return array 34 | */ 35 | protected function getPackageProviders($app): array 36 | { 37 | return [ 38 | AdminUIServiceProvider::class, 39 | ]; 40 | } 41 | 42 | public function disableExceptionHandling(): void 43 | { 44 | $this->app->instance(ExceptionHandler::class, new class extends Handler { 45 | public function __construct() {} 46 | 47 | public function report(Exception $e) 48 | { 49 | // no-op 50 | } 51 | 52 | public function render($request, Exception $e) { 53 | throw $e; 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /resources/views/admin/includes/media-uploader.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if($mediaCollection->isImage()) 3 | 4 | @else 5 | 6 | @endif 7 | 8 | @if(isset($label)) 9 | {{ $label }} 10 | @endif 11 | 12 | @if($mediaCollection->getMaxNumberOfFiles()) 13 | {{ trans('brackets/admin-ui::admin.media_uploader.max_number_of_files', ['maxNumberOfFiles' => $mediaCollection->getMaxNumberOfFiles()]) }} 14 | @endif 15 | @if($mediaCollection->getMaxFileSize()) 16 | {{ trans('brackets/admin-ui::admin.media_uploader.max_size_pre_file', ['maxFileSize' => number_format($mediaCollection->getMaxFileSize()/1024/1024, 2)]) }} 17 | @endif 18 | 19 | @if($mediaCollection->isPrivate()) 20 | 21 | @endif 22 |
23 | 24 | getMaxNumberOfFiles()) 29 | :max-number-of-files="{{ $mediaCollection->getMaxNumberOfFiles() }}" 30 | @endif 31 | @if($mediaCollection->getMaxFileSize()) 32 | :max-file-size-in-mb="{{ round(($mediaCollection->getMaxFileSize()/1024/1024), 2) }}" 33 | @endif 34 | @if($mediaCollection->getAcceptedFileTypes()) 35 | :accepted-file-types="'{{ implode(',', $mediaCollection->getAcceptedFileTypes()) }}'" 36 | @endif 37 | @if(isset($media) && $media->count() > 0) 38 | :uploaded-images="{{ $media->toJson() }}" 39 | @endif 40 | > -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brackets/admin-ui", 3 | "description": "Administration user interface template", 4 | "keywords": [ 5 | "admin-ui", 6 | "template", 7 | "administration", 8 | "admin", 9 | "cms", 10 | "laravel" 11 | ], 12 | "license": "MIT", 13 | "type": "project", 14 | "authors": [ 15 | { 16 | "name": "Pavol Perdík", 17 | "email": "pavol.perdik@brackets.sk", 18 | "homepage": "https://www.brackets.sk", 19 | "role": "Developer" 20 | }, 21 | { 22 | "name": "Matej Minár", 23 | "email": "matej.minar@brackets.sk", 24 | "homepage": "https://www.brackets.sk", 25 | "role": "Developer" 26 | }, 27 | { 28 | "name": "Dušan Subally", 29 | "email": "dusan.subally@triad.sk", 30 | "homepage": "https://www.brackets.sk", 31 | "role": "Developer" 32 | } 33 | ], 34 | "require": { 35 | "php": "^7.2.5|^7.3.0|^8.0|^8.1|^8.2", 36 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0" 37 | }, 38 | "require-dev": { 39 | "orchestra/testbench-browser-kit": "~4.0|~5.0|~6.0|~7.0|~8.0", 40 | "phpunit/phpunit": "^8.5|^9.0|^10.0", 41 | "mockery/mockery": "0.9.*|^1.0" 42 | }, 43 | "autoload": { 44 | "classmap": [], 45 | "psr-4": { 46 | "Brackets\\AdminUI\\": "src/" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "Brackets\\AdminUI\\Tests\\": "tests/" 52 | } 53 | }, 54 | "extra": { 55 | "laravel": { 56 | "providers": [ 57 | "Brackets\\AdminUI\\AdminUIServiceProvider" 58 | ] 59 | }, 60 | "branch-alias": { 61 | "dev-master": "4.0-dev" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Http/Controllers/WysiwygMediaUploadController.php: -------------------------------------------------------------------------------- 1 | file('fileToUpload'); 20 | if (!$temporaryFile->isFile() || !in_array($temporaryFile->getMimeType(), ['image/png', 'image/jpeg', 'image/gif', 'image/svg+xml'])) { 21 | return response()->json([ 22 | 'success' => false 23 | ]); 24 | } 25 | 26 | // generate path that it will be saved to 27 | $savedPath = Config::get('wysiwyg-media.media_folder') . '/' . time() . $temporaryFile->getClientOriginalName(); 28 | 29 | // create directory in which we will be uploading into 30 | if (!File::isDirectory(Config::get('wysiwyg-media.media_folder'))) { 31 | File::makeDirectory(Config::get('wysiwyg-media.media_folder'), 0755, true); 32 | } 33 | 34 | // resize and save image 35 | Image::make($temporaryFile->path()) 36 | ->resize(Config::get('wysiwyg-media.maximum_image_width'), null, function ($constraint) { 37 | $constraint->aspectRatio(); 38 | $constraint->upsize(); 39 | }) 40 | ->save($savedPath); 41 | 42 | // optimize image 43 | OptimizerChainFactory::create()->optimize($savedPath); 44 | 45 | // create related model 46 | $wysiwygMedia = WysiwygMedia::create(['file_path' => $savedPath]); 47 | 48 | // return image's path to use in wysiwyg 49 | return response()->json([ 50 | 'file' => url($savedPath), 51 | 'mediaId' => $wysiwygMedia->id, 52 | 'success' => true 53 | ]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/AdminUIServiceProvider.php: -------------------------------------------------------------------------------- 1 | commands([ 17 | AdminUIInstall::class, 18 | ]); 19 | 20 | $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'brackets/admin-ui'); 21 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'brackets/admin-ui'); 22 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php'); 23 | 24 | if ($this->app->runningInConsole()) { 25 | $this->publishes([ 26 | __DIR__.'/../install-stubs/resources/js/admin' => resource_path('js/admin'), 27 | __DIR__.'/../install-stubs/resources/sass/admin' => resource_path('sass/admin') 28 | ], 'assets'); 29 | 30 | $this->publishes([ 31 | __DIR__.'/../install-stubs/resources/views' => resource_path('views') 32 | ], 'views'); 33 | 34 | $this->publishes([ 35 | __DIR__ . '/../install-stubs/config/wysiwyg-media.php' => config_path('wysiwyg-media.php'), 36 | ], 'config'); 37 | 38 | if (!File::exists(base_path('webpack.mix.js'))){ 39 | $this->publishes([ 40 | __DIR__ . '/../install-stubs/webpack.mix.js' => base_path('webpack.mix.js'), 41 | ], 'webpack'); 42 | } 43 | 44 | if (!glob(base_path('database/migrations/*_create_wysiwyg_media_table.php'))) { 45 | $this->publishes([ 46 | __DIR__ . '/../install-stubs/database/migrations/create_wysiwyg_media_table.php' => database_path('migrations').'/2018_07_18_000000_create_wysiwyg_media_table.php', 47 | ], 'migrations'); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Register any application services. 54 | * 55 | * @return void 56 | */ 57 | public function register() 58 | { 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /resources/views/admin/partials/header.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Console/Commands/AdminUIInstall.php: -------------------------------------------------------------------------------- 1 | info('Installing package brackets/admin-ui'); 31 | 32 | $this->frontendAdjustments($files); 33 | 34 | $this->call('vendor:publish', [ 35 | '--provider' => "Brackets\\AdminUI\\AdminUIServiceProvider", 36 | ]); 37 | 38 | $this->info('Package brackets/admin-ui installed'); 39 | } 40 | 41 | private function strReplaceInFile($fileName, $ifExistsRegex, $find, $replaceWith) { 42 | $content = File::get($fileName); 43 | if (preg_match($ifExistsRegex, $content)) { 44 | return; 45 | } 46 | 47 | return File::put($fileName, str_replace($find, $replaceWith, $content)); 48 | } 49 | 50 | private function appendIfNotExists($fileName, $ifExistsRegex, $append) { 51 | $content = File::get($fileName); 52 | if (preg_match($ifExistsRegex, $content)) { 53 | return; 54 | } 55 | 56 | return File::put($fileName, $content.$append); 57 | } 58 | 59 | /** 60 | * @param Filesystem $files 61 | */ 62 | private function frontendAdjustments(Filesystem $files) { 63 | // webpack 64 | if (File::exists(base_path('webpack.mix.js')) && $this->appendIfNotExists('webpack.mix.js', '|resources/js/admin|', "\n\n" . $files->get(__DIR__ . '/../../../install-stubs/partial-webpack.mix.js'))) { 65 | $this->info('Webpack configuration updated'); 66 | } 67 | 68 | //Change package.json 69 | $this->info('Changing package.json'); 70 | $packageJsonFile = base_path('package.json'); 71 | $packageJson = $files->get($packageJsonFile); 72 | $packageJsonContent = json_decode($packageJson, JSON_OBJECT_AS_ARRAY); 73 | 74 | if (!File::exists('webpack.mix.js')){ 75 | $packageJsonContent['scripts']['craftable-dev'] = 'mix'; 76 | $packageJsonContent['scripts']['craftable-watch'] = 'mix watch'; 77 | $packageJsonContent['scripts']['craftable-prod'] = 'mix --production'; 78 | $packageJsonContent['devDependencies']['laravel-mix'] = '^6.0.6'; 79 | } 80 | 81 | $packageJsonContent['devDependencies']['craftable'] = '^2.1.3'; 82 | $packageJsonContent['devDependencies']['vue-loader'] = '^15.9.8'; 83 | $packageJsonContent['devDependencies']['sass-loader'] = '^8.0.2'; 84 | $packageJsonContent['devDependencies']['resolve-url-loader'] = '^3.1.0'; 85 | $packageJsonContent['devDependencies']['sass'] = '^1.32.6'; 86 | unset($packageJsonContent['type']); 87 | 88 | $files->put($packageJsonFile, json_encode($packageJsonContent, JSON_PRETTY_PRINT)); 89 | $this->info('package.json changed'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /resources/lang/sk/admin.php: -------------------------------------------------------------------------------- 1 | 'Craftable', 5 | 6 | 'operation' => [ 7 | 'succeeded' => 'Akcia prebehla úspešne', 8 | 'failed' => 'Akcia sa nepodarila', 9 | 'not_allowed' => 'Operácia nie je povolená', 10 | 'publish_now' => 'Publikovať', 11 | 'unpublish_now' => 'Zrušiť publikovanie', 12 | 'publish_later' => 'Publikovať neskôr', 13 | ], 14 | 15 | 'dialogs' => [ 16 | 'duplicateDialog' => [ 17 | 'title' => 'Varovanie!', 18 | 'text' => 'Naozaj chcete duplikovať túto položku?', 19 | 'yes' => 'Áno, duplikovať.', 20 | 'no' => 'Nie, zatvoriť.', 21 | 'success_title' => 'Úspešne!', 22 | 'success' => 'Položka úspešne duplikovaná.', 23 | 'error_title' => 'Chyba!', 24 | 'error' => 'Došlo k chybe.', 25 | ], 26 | 'deleteDialog' => [ 27 | 'title' => 'Varovanie!', 28 | 'text' => 'Naozaj chcete vymazať túto položku?', 29 | 'yes' => 'Áno, vymazať.', 30 | 'no' => 'Nie, zatvoriť.', 31 | 'success_title' => 'Úspešne!', 32 | 'success' => 'Položka úspešne odstránená.', 33 | 'error_title' => 'Chyba!', 34 | 'error' => 'Došlo k chybe.', 35 | ], 36 | 'publishNowDialog' => [ 37 | 'title' => 'Varovanie!', 38 | 'text' => 'Naozaj chcete publikovať túto položku?', 39 | 'yes' => 'Áno, publikovať.', 40 | 'no' => 'Nie, zatvoriť.', 41 | 'success_title' => 'Úspešne!', 42 | 'success' => 'Položka úspešne publikovaná.', 43 | 'error_title' => 'Chyba!', 44 | 'error' => 'Došlo k chybe.', 45 | ], 46 | 'unpublishNowDialog' => [ 47 | 'title' => 'Varovanie!', 48 | 'text' => 'Naozaj chcete zrušiť publikovanie tejto položky?', 49 | 'yes' => 'Áno, zrušiť puklikovanie.', 50 | 'no' => 'Nie, zatvoriť.', 51 | 'success_title' => 'Úspešne!', 52 | 'success' => 'Položka úspešne publikovaná.', 53 | 'error_title' => 'Chyba!', 54 | 'error' => 'Došlo k chybe.', 55 | ], 56 | 'publishLaterDialog' => [ 57 | 'text' => 'Prosím vyberťe dátum kedy má byť pooložka publikovaná:', 58 | 'yes' => 'Uložiť', 59 | 'no' => 'Zatvoriť', 60 | 'success_title' => 'Úspešne!', 61 | 'success' => 'Položka bola úspešne uložená.', 62 | 'error_title' => 'Chyba!', 63 | 'error' => 'Došlo k chybe.', 64 | ], 65 | ], 66 | 67 | 'btn' => [ 68 | 'save' => 'Uložiť', 69 | 'cancel' => 'Zrušiť', 70 | 'edit' => 'Upraviť', 71 | 'delete' => 'Vymazať', 72 | 'search' => 'Hľadať', 73 | 'saved' => 'Uložené', 74 | ], 75 | 76 | 'index' => [ 77 | 'no_items' => 'Nenašli sa žiadne položky', 78 | 'try_changing_items' => 'Skúste zmeniť filter alebo pridať novú', 79 | ], 80 | 81 | 'listing' => [ 82 | 'selected_items' => 'Vybrané položky', 83 | 'uncheck_all_items' => 'Odznačiť všetky položky', 84 | 'check_all_items' => 'Označiť všetky položky', 85 | ], 86 | 87 | 'forms' => [ 88 | 'select_a_date' => 'Zvoľte dátum', 89 | 'select_a_time' => 'Zvoľte čas', 90 | 'select_date_and_time' => 'Zvoľte dátum a čas', 91 | 'choose_translation_to_edit' => 'Zvoľte preklad na úpravu', 92 | 'manage_translations' => 'Spravovať preklady', 93 | 'more_can_be_managed' => '({{ otherLocales.length }} možno spravovať)', 94 | 'currently_editing_translation' => 'Práve upravujete {{ this.defaultLocale.toUpperCase() }} (základný) preklad', 95 | 'hide' => 'Skryť preklady', 96 | 'publish' => 'Publikácia', 97 | 'history' => 'História', 98 | 'created_by' => 'Vytvoril', 99 | 'updated_by' => 'Aktualizoval', 100 | 'created_on' => 'Vytvorené', 101 | 'updated_on' => 'Aktualizované' 102 | ], 103 | 104 | 'placeholder' => [ 105 | 'search' => 'Hľadať' 106 | ], 107 | 108 | 'pagination' => [ 109 | 'overview' => 'Zobrazujú sa položky od {{ pagination.state.from }} do {{ pagination.state.to }} z celkom {{ pagination.state.total }} položiek.' 110 | ], 111 | 112 | 'logo' => [ 113 | 'title' => 'Craftable', 114 | ], 115 | 116 | 'profile_dropdown' => [ 117 | 'account' => 'Účet', 118 | ], 119 | 120 | 'sidebar' => [ 121 | 'content' => 'Obsah', 122 | 'settings' => 'Nastavenia', 123 | ], 124 | 125 | 'media_uploader' => [ 126 | 'max_number_of_files' => '(max. počet súborov: :maxNumberOfFiles files)', 127 | 'max_size_pre_file' => '(max. veľkosť súboru: :maxFileSize MB)', 128 | 129 | 'private_title' => 'Súbory nie sú verejne dostupné.', 130 | ], 131 | 132 | 'footer' => [ 133 | 'powered_by' => 'Powered by', // we leave this in english intentionally 134 | ] 135 | 136 | ]; -------------------------------------------------------------------------------- /resources/lang/en/admin.php: -------------------------------------------------------------------------------- 1 | 'Craftable', 5 | 6 | 'operation' => [ 7 | 'succeeded' => 'Operation successful', 8 | 'failed' => 'Operation failed', 9 | 'not_allowed' => 'Operation not allowed', 10 | 'publish_now' => 'Publish now', 11 | 'unpublish_now' => 'Unpublish now', 12 | 'publish_later' => 'Publish later', 13 | ], 14 | 15 | 'dialogs' => [ 16 | 'duplicateDialog' => [ 17 | 'title' => 'Warning!', 18 | 'text' => 'Do you really want to duplicate this item?', 19 | 'yes' => 'Yes, duplicate.', 20 | 'no' => 'No, cancel.', 21 | 'success_title' => 'Success!', 22 | 'success' => 'Item successfully duplicated.', 23 | 'error_title' => 'Error!', 24 | 'error' => 'An error has occured.', 25 | ], 26 | 'deleteDialog' => [ 27 | 'title' => 'Warning!', 28 | 'text' => 'Do you really want to delete this item?', 29 | 'yes' => 'Yes, delete.', 30 | 'no' => 'No, cancel.', 31 | 'success_title' => 'Success!', 32 | 'success' => 'Item successfully deleted.', 33 | 'error_title' => 'Error!', 34 | 'error' => 'An error has occured.', 35 | ], 36 | 'publishNowDialog' => [ 37 | 'title' => 'Warning!', 38 | 'text' => 'Do you really want to publish this item now?', 39 | 'yes' => 'Yes, publish now.', 40 | 'no' => 'No, cancel.', 41 | 'success_title' => 'Success!', 42 | 'success' => 'Item successfully published.', 43 | 'error_title' => 'Error!', 44 | 'error' => 'An error has occured.', 45 | ], 46 | 'unpublishNowDialog' => [ 47 | 'title' => 'Warning!', 48 | 'text' => 'Do you really want to unpublish this item now?', 49 | 'yes' => 'Yes, unpublish now.', 50 | 'no' => 'No, cancel.', 51 | 'success_title' => 'Success!', 52 | 'success' => 'Item successfully published.', 53 | 'error_title' => 'Error!', 54 | 'error' => 'An error has occured.', 55 | ], 56 | 'publishLaterDialog' => [ 57 | 'text' => 'Please choose the date when the item should be published:', 58 | 'yes' => 'Save', 59 | 'no' => 'Cancel', 60 | 'success_title' => 'Success!', 61 | 'success' => 'Item was successfully saved.', 62 | 'error_title' => 'Error!', 63 | 'error' => 'An error has occured.', 64 | ], 65 | ], 66 | 67 | 'btn' => [ 68 | 'save' => 'Save', 69 | 'cancel' => 'Cancel', 70 | 'edit' => 'Edit', 71 | 'delete' => 'Delete', 72 | 'search' => 'Search', 73 | 'new' => 'New', 74 | 'saved' => 'Saved', 75 | ], 76 | 77 | 'index' => [ 78 | 'no_items' => 'Could not find any items', 79 | 'try_changing_items' => 'Try changing the filters or add a new one', 80 | ], 81 | 82 | 'listing' => [ 83 | 'selected_items' => 'Selected items', 84 | 'uncheck_all_items' => 'Uncheck all items', 85 | 'check_all_items' => 'Check all items', 86 | ], 87 | 88 | 'forms' => [ 89 | 'select_a_date' => 'Select date', 90 | 'select_a_time' => 'Select time', 91 | 'select_date_and_time' => 'Select date and time', 92 | 'choose_translation_to_edit' => 'Choose translation to edit:', 93 | 'manage_translations' => 'Manage translations', 94 | 'more_can_be_managed' => '({{ otherLocales.length }} more can be managed)', 95 | 'currently_editing_translation' => 'Currently editing {{ this.defaultLocale.toUpperCase() }} (default) translation', 96 | 'hide' => 'Hide Translations', 97 | 'select_an_option' => 'Select an option', 98 | 'select_options' => 'Select options', 99 | 'publish' => 'Publish', 100 | 'history' => 'History', 101 | 'created_by' => 'Created by', 102 | 'updated_by' => 'Updated by', 103 | 'created_on' => 'Created on', 104 | 'updated_on' => 'Updated on' 105 | ], 106 | 107 | 'placeholder' => [ 108 | 'search' => 'Search' 109 | ], 110 | 111 | 'pagination' => [ 112 | 'overview' => 'Displaying items from {{ pagination.state.from }} to {{ pagination.state.to }} of total {{ pagination.state.total }} items.' 113 | ], 114 | 115 | 'logo' => [ 116 | 'title' => 'Craftable', 117 | ], 118 | 119 | 'profile_dropdown' => [ 120 | 'account' => 'Account', 121 | ], 122 | 123 | 'sidebar' => [ 124 | 'content' => 'Content', 125 | 'settings' => 'Settings', 126 | ], 127 | 128 | 'media_uploader' => [ 129 | 'max_number_of_files' => '(max no. of files: :maxNumberOfFiles files)', 130 | 'max_size_pre_file' => '(max size per file: :maxFileSize MB)', 131 | 132 | 'private_title' => 'Files are not accessible for public', 133 | ], 134 | 135 | 'footer' => [ 136 | 'powered_by' => 'Powered by', 137 | ] 138 | 139 | ]; -------------------------------------------------------------------------------- /install-stubs/resources/views/admin/layout/logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- You may use plain text as a logo instead of image --}} 3 | Craftable 4 | 5 | {{--Text Logo--}} 6 | 7 | -------------------------------------------------------------------------------- /install-stubs/resources/views/vendor/mail/html/themes/default.css: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | 3 | body, body *:not(html):not(style):not(br):not(tr):not(code) { 4 | font-family: Avenir, Helvetica, sans-serif; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | background-color: #4A6DF5; 10 | color: #74787E; 11 | height: 100%; 12 | hyphens: auto; 13 | line-height: 1.4; 14 | margin: 0; 15 | -moz-hyphens: auto; 16 | -ms-word-break: break-all; 17 | width: 100% !important; 18 | -webkit-hyphens: auto; 19 | -webkit-text-size-adjust: none; 20 | word-break: break-all; 21 | word-break: break-word; 22 | } 23 | 24 | p, 25 | ul, 26 | ol, 27 | blockquote { 28 | line-height: 1.4; 29 | text-align: left; 30 | } 31 | 32 | a { 33 | color: #3869D4; 34 | } 35 | 36 | a img { 37 | border: none; 38 | } 39 | 40 | /* Typography */ 41 | 42 | h1 { 43 | color: #2F3133; 44 | font-size: 19px; 45 | font-weight: bold; 46 | margin-top: 0; 47 | text-align: left; 48 | } 49 | 50 | h2 { 51 | color: #2F3133; 52 | font-size: 16px; 53 | font-weight: bold; 54 | margin-top: 0; 55 | text-align: left; 56 | } 57 | 58 | h3 { 59 | color: #2F3133; 60 | font-size: 14px; 61 | font-weight: bold; 62 | margin-top: 0; 63 | text-align: left; 64 | } 65 | 66 | p { 67 | color: #74787E; 68 | font-size: 16px; 69 | line-height: 1.5em; 70 | margin-top: 0; 71 | text-align: left; 72 | hyphens: none; 73 | } 74 | 75 | p.sub { 76 | font-size: 12px; 77 | } 78 | 79 | img { 80 | max-width: 100%; 81 | } 82 | 83 | /* Layout */ 84 | 85 | .wrapper { 86 | background-color: #4A6DF5; 87 | margin: 0; 88 | padding: 0; 89 | width: 100%; 90 | -premailer-cellpadding: 0; 91 | -premailer-cellspacing: 0; 92 | -premailer-width: 100%; 93 | } 94 | 95 | .content { 96 | margin: 0; 97 | padding: 0; 98 | width: 100%; 99 | -premailer-cellpadding: 0; 100 | -premailer-cellspacing: 0; 101 | -premailer-width: 100%; 102 | } 103 | 104 | /* Header */ 105 | 106 | .header { 107 | padding: 25px 0; 108 | text-align: center; 109 | } 110 | 111 | .header a { 112 | color: white; 113 | font-size: 19px; 114 | font-weight: bold; 115 | text-decoration: none; 116 | } 117 | 118 | /* Body */ 119 | 120 | .body { 121 | background-color: #FFFFFF; 122 | border-bottom: 1px solid #EDEFF2; 123 | border-top: 1px solid #EDEFF2; 124 | margin: 0; 125 | padding: 0; 126 | width: 100%; 127 | -premailer-cellpadding: 0; 128 | -premailer-cellspacing: 0; 129 | -premailer-width: 100%; 130 | } 131 | 132 | .inner-body { 133 | background-color: #FFFFFF; 134 | margin: 0 auto; 135 | padding: 0; 136 | width: 570px; 137 | -premailer-cellpadding: 0; 138 | -premailer-cellspacing: 0; 139 | -premailer-width: 570px; 140 | } 141 | 142 | /* Subcopy */ 143 | 144 | .subcopy { 145 | border-top: 1px solid #EDEFF2; 146 | margin-top: 25px; 147 | padding-top: 25px; 148 | } 149 | 150 | .subcopy p { 151 | font-size: 12px; 152 | } 153 | 154 | /* Footer */ 155 | 156 | .footer { 157 | margin: 0 auto; 158 | padding: 0; 159 | text-align: center; 160 | width: 570px; 161 | -premailer-cellpadding: 0; 162 | -premailer-cellspacing: 0; 163 | -premailer-width: 570px; 164 | } 165 | 166 | .footer p { 167 | color: white; 168 | font-size: 12px; 169 | text-align: center; 170 | } 171 | 172 | /* Tables */ 173 | 174 | .table table { 175 | margin: 30px auto; 176 | width: 100%; 177 | -premailer-cellpadding: 0; 178 | -premailer-cellspacing: 0; 179 | -premailer-width: 100%; 180 | } 181 | 182 | .table th { 183 | border-bottom: 1px solid #EDEFF2; 184 | padding-bottom: 8px; 185 | } 186 | 187 | .table td { 188 | color: #74787E; 189 | font-size: 15px; 190 | line-height: 18px; 191 | padding: 10px 0; 192 | } 193 | 194 | .content-cell { 195 | padding: 35px; 196 | } 197 | 198 | /* Buttons */ 199 | 200 | .action { 201 | margin: 30px auto; 202 | padding: 0; 203 | text-align: center; 204 | width: 100%; 205 | -premailer-cellpadding: 0; 206 | -premailer-cellspacing: 0; 207 | -premailer-width: 100%; 208 | } 209 | 210 | .button { 211 | border-radius: 3px; 212 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); 213 | color: #FFF; 214 | display: inline-block; 215 | text-decoration: none; 216 | -webkit-text-size-adjust: none; 217 | } 218 | 219 | .button-blue, 220 | .button-primary { 221 | background-color: #4A6DF5; 222 | border-top: 10px solid #4A6DF5; 223 | border-right: 18px solid #4A6DF5; 224 | border-bottom: 10px solid #4A6DF5; 225 | border-left: 18px solid #4A6DF5; 226 | } 227 | 228 | .button-green, 229 | .button-success { 230 | background-color: #60CF7D; 231 | border-top: 10px solid #60CF7D; 232 | border-right: 18px solid #60CF7D; 233 | border-bottom: 10px solid #60CF7D; 234 | border-left: 18px solid #60CF7D; 235 | } 236 | 237 | .button-red, 238 | .button-error { 239 | background-color: #E03441; 240 | border-top: 10px solid #E03441; 241 | border-right: 18px solid #E03441; 242 | border-bottom: 10px solid #E03441; 243 | border-left: 18px solid #E03441; 244 | } 245 | 246 | /* Panels */ 247 | 248 | .panel { 249 | margin: 0 0 21px; 250 | } 251 | 252 | .panel-content { 253 | background-color: #EDEFF2; 254 | padding: 16px; 255 | } 256 | 257 | .panel-item { 258 | padding: 0; 259 | } 260 | 261 | .panel-item p:last-of-type { 262 | margin-bottom: 0; 263 | padding-bottom: 0; 264 | } 265 | 266 | /* Promotions */ 267 | 268 | .promotion { 269 | background-color: #FFFFFF; 270 | border: 1px dashed #9BA2AB; 271 | margin: 0; 272 | margin-bottom: 25px; 273 | margin-top: 25px; 274 | padding: 24px; 275 | width: 100%; 276 | -premailer-cellpadding: 0; 277 | -premailer-cellspacing: 0; 278 | -premailer-width: 100%; 279 | } 280 | 281 | .promotion h1 { 282 | text-align: center; 283 | } 284 | 285 | .promotion p { 286 | font-size: 15px; 287 | text-align: center; 288 | } 289 | -------------------------------------------------------------------------------- /install-stubs/resources/sass/admin/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary: #4273FA; 3 | $danger: #FE3F61; 4 | $success: #60CF7D; 5 | $warning: #F5BE30; 6 | $mutedText: #b9c8de; 7 | 8 | $sidebar-bg: #485779; 9 | $sidebar-nav-link-active-bg: $primary; 10 | $sidebar-nav-link-active-icon-color: white; 11 | $pagination-hover-bg: #f3f8ff; 12 | 13 | // Available loaders 14 | $audio: url('data:image/svg+xml;utf8,') !default; 15 | $ballTriangle: url('data:image/svg+xml;utf8,') !default; 16 | $bars: url('data:image/svg+xml;utf8,') !default; 17 | $circles: url('data:image/svg+xml;utf8,') !default; 18 | $grid: url('data:image/svg+xml;utf8,') !default; 19 | $hearts: url('data:image/svg+xml;utf8,') !default; 20 | $oval: url('data:image/svg+xml;utf8,') !default; 21 | $puff: url('data:image/svg+xml;utf8,') !default; 22 | $rings: url('data:image/svg+xml;utf8,') !default; 23 | $threeDots: url('data:image/svg+xml;utf8,') !default; 24 | 25 | // Active loader 26 | $loader: $threeDots; 27 | -------------------------------------------------------------------------------- /resources/views/admin/partials/wysiwyg-svgs.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
--------------------------------------------------------------------------------