diff --git a/app/Dashboard.php b/app/Dashboard.php index 706e338..b6d3b6b 100644 --- a/app/Dashboard.php +++ b/app/Dashboard.php @@ -70,38 +70,19 @@ class Dashboard */ public static function getModel($model, $type = null) { - $model_name = ucfirst($model); + $model_name = implode('', array_map('ucfirst', explode('_', $model))); - // Ensure the model has been declared in the menu - $model_in_menu = false; - - foreach (self::$menu as $menu_item) { - if (array_key_exists('submenu', $menu_item)) { - // Check each item if this is a submenu - foreach ($menu_item['submenu'] as $submenu_item) { - if ($submenu_item['model'] == $model) { - $model_in_menu = true; - break; - } - } - } else { - // Check the menu item - if ($menu_item['model'] == $model) { - $model_in_menu = true; - } - } - - // Don't bother continuing if we've already confirmed it's in the menu - if ($model_in_menu) { - break; - } - } - - if ($model_in_menu && file_exists(app_path() . '/Models/' . $model_name . '.php')) { + if (file_exists(app_path() . '/Models/' . $model_name . '.php')) { $model_class = 'App\\Models\\' . $model_name; - if ($type != null && $type != $model_class::$dashboard_type) { - return null; + if ($type != null) { + if (is_array($type)) { + if (!in_array($model_class::$dashboard_type, $type)) { + return null; + } + } else if ($type != $model_class::$dashboard_type) { + return null; + } } return new $model_class; diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index bb231d1..e88a8d4 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -82,18 +82,11 @@ class DashboardController extends Controller { if ($model_class != null) { if ($id == 'new') { - $item = null; + $item = new $model_class; } else { if ($model_class::where('id', $id)->exists()) { $item = $model_class::find($id); - foreach ($model_class::$dashboard_columns as $column) { - if ($column['type'] === 'list') { - $list_model_class = 'App\\Models\\' . $column['model']; - $item->{$column['name']} = $list_model_class::where($column['foreign'], $item->id)->orderBy($column['sort'])->get(); - } - } - if (is_null($item) || !$item->userCheck()) { return view('errors.no-such-record'); } @@ -102,13 +95,26 @@ class DashboardController extends Controller { } } + foreach ($model_class::$dashboard_columns as $column) { + if ($column['type'] === 'list') { + $list_model_class = 'App\\Models\\' . $column['model']; + $list_model_instance = new $list_model_class; + + $item->{$column['name']} = [ + 'model' => $list_model_instance->getTable(), + 'list' => $id == 'new' ? [] : $list_model_instance::where($column['foreign'], $item->id)->orderBy($column['sort'])->get() + ]; + } + } + return view('dashboard.pages.edit-item', [ - 'heading' => $model_class->getDashboardHeading(), - 'model' => $model, - 'id' => $id, - 'item' => $item, - 'help_text' => $model_class::$dashboard_help_text, - 'columns' => $model_class::$dashboard_columns + 'heading' => $model_class->getDashboardHeading(), + 'default_img_ext' => $model_class::$default_image_ext, + 'model' => $model, + 'id' => $id, + 'item' => $item, + 'help_text' => $model_class::$dashboard_help_text, + 'columns' => $model_class::$dashboard_columns ]); } else { abort(404); @@ -242,6 +248,8 @@ class DashboardController extends Controller { } // populate connected lists with list items in $request + $lists = []; + foreach ($request['columns'] as $column) { if ($column['type'] === 'list') { $column_name = $column['name']; @@ -250,20 +258,44 @@ class DashboardController extends Controller { if ($dashboard_column['name'] === $column_name) { $foreign = $dashboard_column['foreign']; $list_model_class = 'App\\Models\\' . $dashboard_column['model']; - $list_model_class::where($foreign, $item->id)->delete(); - if ($request->has($column_name)) { - foreach ($request[$column_name] as $index => $row) { - $list_model_item = new $list_model_class; - $list_model_item->$foreign = $item->id; - $list_model_item->{$dashboard_column['sort']} = $index; + if ($list_model_class::$dashboard_type == 'list') { + $ids = []; - foreach ($row as $key => $value) { - $list_model_item->$key = $value; + if ($request->has($column_name)) { + foreach ($request[$column_name] as $index => $row) { + if ($row['id'] == 'new') { + $list_model_item = new $list_model_class; + } else { + $list_model_item = $list_model_class::find($row['id']); + } + + $list_model_item->$foreign = $item->id; + $list_model_item->{$dashboard_column['sort']} = $index; + + foreach ($row['data'] as $key => $data) { + if ($data['type'] == 'string') { + $list_model_item->$key = $data['value']; + } + } + + $list_model_item->save(); + array_push($ids, $list_model_item->id); } - - $list_model_item->save(); } + + // delete any associated row that wasn't just created or edited + foreach ($list_model_class::where($foreign, $item->id)->whereNotIn('id', $ids)->get() as $list_item) { + $list_item->delete(); + } + + // store the sets of ids for each list + $lists[$column_name] = $ids; + + // stop looping through dashboard columns + break; + } else { + return 'invalid-list-model:' . $dashboard_column['model']; } } } @@ -274,7 +306,10 @@ class DashboardController extends Controller { $item->save(); // return the id number in the format '^id:[0-9][0-9]*$' on success - return 'id:' . $item->id; + return [ + 'id' => $item->id, + 'lists' => $lists + ]; } else { return 'model-access-fail'; } @@ -289,7 +324,7 @@ class DashboardController extends Controller { 'name' => 'required' ]); - $model_class = Dashboard::getModel($request['model'], 'edit'); + $model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]); if ($model_class != null) { $item = $model_class::find($request['id']); @@ -321,7 +356,7 @@ class DashboardController extends Controller { 'name' => 'required' ]); - $model_class = Dashboard::getModel($request['model'], 'edit'); + $model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]); if ($model_class != null) { $item = $model_class::find($request['id']); @@ -363,12 +398,16 @@ class DashboardController extends Controller { return 'permission-fail'; } - // delete associated files if they exist - foreach ($model_class::$dashboard_columns as $column) { - if ($column['type'] == 'image') { - $item->deleteImage($column['name'], false); - } else if ($column['type'] == 'file') { - $item->deleteFile($column['name'], false); + // delete associated list items + foreach ($item::$dashboard_columns as $column) { + if ($column['type'] == 'list') { + $list_model_class = Dashboard::getModel($column['model'], 'list'); + + if ($list_model_class != null) { + foreach ($list_model_class::where($column['foreign'], $item->id)->get() as $list_item) { + $list_item->delete(); + } + } } } @@ -399,7 +438,7 @@ class DashboardController extends Controller { 'name' => 'required' ]); - $model_class = Dashboard::getModel($request['model'], 'edit'); + $model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]); if ($model_class != null) { $item = $model_class::find($request['id']); @@ -423,7 +462,7 @@ class DashboardController extends Controller { 'name' => 'required' ]); - $model_class = Dashboard::getModel($request['model'], 'edit'); + $model_class = Dashboard::getModel($request['model'], [ 'edit', 'list' ]); if ($model_class != null) { $item = $model_class::find($request['id']); diff --git a/app/Models/Blog.php b/app/Models/Blog.php index 39e5033..82eb5ca 100644 --- a/app/Models/Blog.php +++ b/app/Models/Blog.php @@ -21,8 +21,8 @@ class Blog extends DashboardModel [ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ], [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], - [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ], - [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ] + [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true, 'ext' => 'jpg' ], + [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'foreign' => 'blog_id', 'sort' => 'order' ] ]; public static function getBlogEntries() diff --git a/app/Models/BlogTags.php b/app/Models/BlogTags.php index ad94fd5..47e38bd 100644 --- a/app/Models/BlogTags.php +++ b/app/Models/BlogTags.php @@ -2,8 +2,8 @@ use Illuminate\Database\Eloquent\Model; -class BlogTags extends Model { - +class BlogTags extends DashboardModel +{ /** * The database table used by the model. * @@ -11,4 +11,9 @@ class BlogTags extends Model { */ protected $table = 'blog_tags'; + public static $dashboard_type = 'list'; + + public static $dashboard_columns = [ + [ 'type' => 'string', 'name' => 'name' ] + ]; } diff --git a/app/Models/DashboardModel.php b/app/Models/DashboardModel.php index 4f20881..75f3846 100644 --- a/app/Models/DashboardModel.php +++ b/app/Models/DashboardModel.php @@ -117,6 +117,33 @@ class DashboardModel extends Model */ public static $dashboard_id_link = []; + /** + * The default image extension when none is set + * + * @var string + */ + public static $default_image_ext = 'jpg'; + + /** + * Functionality to run when various events occur + * + * @return null + */ + public static function boot() { + parent::boot(); + + static::deleting(function($item) { + // delete associated images and files if they exist + foreach ($item::$dashboard_columns as $column) { + if ($column['type'] == 'image') { + $item->deleteImage($column['name'], false); + } else if ($column['type'] == 'file') { + $item->deleteFile($column['name'], false); + } + } + }); + } + /** * Returns the dashboard heading * @@ -155,7 +182,6 @@ class DashboardModel extends Model $max_width = 0; $max_height = 0; - $main_ext = 'jpg'; // Retrieve the column $column = static::getColumn($name); @@ -165,9 +191,11 @@ class DashboardModel extends Model return 'no-such-column-fail'; } - // Update the extension if it's been configured + // Use the configured image extension or fall back on the default if none is set if (array_key_exists('ext', $column)) { $main_ext = $column['ext']; + } else { + $main_ext = $this::$default_image_ext; } // Create the directory if it doesn't exist @@ -254,7 +282,6 @@ class DashboardModel extends Model } // Set up our variables - $main_ext = 'jpg'; $extensions = []; // Retrieve the column @@ -265,9 +292,11 @@ class DashboardModel extends Model return 'no-such-column-fail'; } - // Update the extension if it's been configured + // Use the configured image extension or fall back on the default if none is set if (array_key_exists('ext', $column)) { $main_ext = $column['ext']; + } else { + $main_ext = $this::$default_image_ext; } // Build the set of extensions to delete diff --git a/readme.md b/readme.md index 1a5453d..f40b717 100644 --- a/readme.md +++ b/readme.md @@ -14,12 +14,13 @@ A Hypothetical website template for bootstrapping new projects. ## Major Components * Bootstrap 5 -* Fontawesome 5 -* Gsap 3 -* Gulp 4 -* Jquery 3 +* Browsersync +* Fontawesome +* Gsap +* Gulp +* Jquery * Laravel 9 -* Sass 1.32 +* Sass * Vue 3 (Optional) ## Setup @@ -71,13 +72,13 @@ Reading through its contents is encouraged for a complete understanding of what * `gulp`: Update the compiled javascript and css in `public/js` and `public/css`, and copy fonts to `public/fonts`. * `gulp --production`: Does the same as `gulp` except the compiled javascript and css is minified, and console logging is removed from the javascript (good for production deployments). -* `gulp default watch`: Does the same as `gulp` but continues running to watch for changes to files so it can recompile updated assets and reload them in the browser using BrowserSync (good for development environments). +* `gulp default watch`: Does the same as `gulp` but continues running to watch for changes to files so it can recompile updated assets and reload them in the browser using Browsersync (good for development environments). **NOTE**: If `gulp` isn't installed globally or its version is less than `4`, you should use the version included in `node_modules` by running `"$(npm bin)/gulp"` in place of the `gulp` command. -### BrowserSync +### Browsersync -BrowserSync is used to keep the browser in sync with your code when running the `watch` task with gulp. +Browsersync is used to keep the browser in sync with your code when running the `watch` task with gulp. ## Public @@ -148,6 +149,13 @@ In a Vue.js component: ## Dashboard +### Important Note + +The naming convention of dashboard database tables and model classes should be the following: + +* Database table names should be lower case with hyphen separators: `your_table_name` +* Model classes should be the same name but in camel case with its first character capitalized: `YourTableName.php` and `class YourTableName extends DashboardModel` + ### Registration The `REGISTRATION` variable in the `.env` file controls whether a new dashboard user can be registered. @@ -229,7 +237,7 @@ Models with their `$dashboard_type` set to `edit` also use: * `date-time`: Date and time selection tool for date/time data * `mkd`: Multi-line text input field with a markdown editor * `select`: Text input via option select - * `list`: One or more items saved to a connected table + * `list`: One or more `text` or `image` items saved to a connected table * `image`: Fields that contain image uploads * `file`: Fields that contains file uploads * `display`: Displayed information that can't be edited @@ -240,9 +248,19 @@ Models with their `$dashboard_type` set to `edit` also use: * `name`: (required by `file` and `image`) Used along with the record id to determine the filename * `delete`: (optional for `file` and `image`) Enables a delete button for the upload when set to true * `ext`: (required by `file` and optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`) +* `model`: (required by `list`) The class name of the model that the list will be generated from +* `foreign` (required by `list`) The name of the list table's foreign id column that references the id on the current table +* `sort` (required by `list`) The name of the list table's column that the order will be stored in * `max_width`: (optional for `image`) Configures the maximum width of an image upload (defaults to `0` which sets no maximum width) * `max_height`: (optional for `image`) Configures the maximum height of an image upload (defaults to `0` which sets no maximum height) +Models with their `$dashboard_type` set to `list` also use: + +* `type`: The column type which can be any of the following: + * `string`: Single-line text input field + * `image`: Fields that contain image uploads +* `ext`: (optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`) + An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`: ```php @@ -262,6 +280,15 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true, 'ext' => 'jpg' ], - [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ] + [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'foreign' => 'blog_id', 'sort' => 'order' ] + ]; +``` + +An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `list`: + +```php + public static $dashboard_columns = [ + [ 'type' => 'string', 'name' => 'name' ], + [ 'type' => 'image', 'name' => 'photo' ] ]; ``` diff --git a/resources/js/dashboard.js b/resources/js/dashboard.js index e7a3884..92d9168 100644 --- a/resources/js/dashboard.js +++ b/resources/js/dashboard.js @@ -323,17 +323,17 @@ function editItemInit() { $currencyInputs = $(".currency-input"), $datePickers = $(".date-picker"), $mkdEditors = $(".mkd-editor"), - $fileUploads = $(".file-upload"), - $imgUploads = $(".image-upload"), $lists = $(".list"), $token = $("#token"), model = $editItem.data("model"), - id = $editItem.data("id"), - operation = id === "new" ? "create" : "update"; + operation = $editItem.data("id") === "new" ? "create" : "update"; - let allowTimes = [], + let $imgUploads = [], + $fileUploads = [], + allowTimes = [], easymde = [], formData = {}, + id = $editItem.data("id"), submitting = false, hours, minutes, @@ -404,30 +404,33 @@ function editItemInit() { value = []; $this.find(".list-items .list-items-row").each(function(index, row) { - const rowData = {}; + const rowData = {}, + id = $(row).data("id"); - $(row).find(".list-items-row-input-inner").each(function(index, input) { - const $input = $(input), - column = $input.data("column"), - value = $input.val(); + $(row).find(".list-items-row-content-inner").each(function(index, inner) { + const $inner = $(inner), + $input = $inner.find(".list-input"), + column = $inner.data("column"); - rowData[column] = value; + if ($inner.data("type") === "string") { + rowData[column] = { type: "string", value: $input.val() }; + } }); - value.push(rowData); + value.push({ id: typeof id === "undefined" ? "new" : id, data: rowData }); }); addFormData("list", column, value); }); }; - const uploadFile = function(row_id, currentFile) { + const uploadFile = function(currentFile) { let file, fileUpload; // functionality to run on success const returnSuccess = function() { loadingModal("hide"); - window.location.href = `/dashboard/edit/${model}/${row_id}`; + window.location.href = `/dashboard/edit/${model}/${id}`; }; // add the file from the file upload box for file-upload class elements @@ -439,9 +442,9 @@ function editItemInit() { // add the file, id and model to the formData variable file.append("file", fileUpload.files[0]); - file.append("name", $(fileUpload).attr("name")); - file.append("id", row_id); - file.append("model", model); + file.append("name", $(fileUpload).data("column")); + file.append("id", $(fileUpload).data("id")); + file.append("model", $(fileUpload).data("model")); $.ajax({ type: "POST", @@ -452,7 +455,7 @@ function editItemInit() { beforeSend: function(xhr) { xhr.setRequestHeader("X-CSRF-TOKEN", $token.val()); } }).always(function(response) { if (response === "success") { - uploadFile(row_id, currentFile + 1); + uploadFile(currentFile + 1); } else { loadingModal("hide"); @@ -462,19 +465,19 @@ function editItemInit() { } }); } else { - uploadFile(row_id, currentFile + 1); + uploadFile(currentFile + 1); } } else { returnSuccess(); } }; - const uploadImage = function(row_id, currentImage) { + const uploadImage = function(currentImage) { let file, imgUpload; // functionality to run on success const returnSuccess = function() { - uploadFile(row_id, 0); + uploadFile(0); }; // add the image from the image upload box for image-upload class elements @@ -486,9 +489,9 @@ function editItemInit() { // add the file, id and model to the formData variable file.append("file", imgUpload.files[0]); - file.append("name", $(imgUpload).attr("name")); - file.append("id", row_id); - file.append("model", model); + file.append("name", $(imgUpload).data("column")); + file.append("id", $(imgUpload).data("id")); + file.append("model", $(imgUpload).data("model")); $.ajax({ type: "POST", @@ -499,7 +502,7 @@ function editItemInit() { beforeSend: function(xhr) { xhr.setRequestHeader("X-CSRF-TOKEN", $token.val()); } }).always(function(response) { if (response === "success") { - uploadImage(row_id, currentImage + 1); + uploadImage(currentImage + 1); } else { loadingModal("hide"); @@ -509,7 +512,7 @@ function editItemInit() { } }); } else { - uploadImage(row_id, currentImage + 1); + uploadImage(currentImage + 1); } } else { returnSuccess(); @@ -524,7 +527,7 @@ function editItemInit() { // initialize image deletion $(".edit-button.delete.image").on("click", function(e) { const $this = $(this), - name = $this.data("name"); + name = $this.data("column"); if (!submitting) { submitting = true; @@ -535,8 +538,8 @@ function editItemInit() { type: "DELETE", url: "/dashboard/image-delete", data: { - id: id, - model: model, + id: $this.data("id"), + model: $this.data("model"), name: name, _token: $token.val() } @@ -559,7 +562,7 @@ function editItemInit() { // initialize file deletion $(".edit-button.delete.file").on("click", function(e) { const $this = $(this), - name = $this.data("name"), + name = $this.data("column"), ext = $this.data("ext"); if (!submitting) { @@ -571,8 +574,8 @@ function editItemInit() { type: "DELETE", url: "/dashboard/file-delete", data: { - id: id, - model: model, + id: $this.data("id"), + model: $this.data("model"), name: name, ext: ext, _token: $token.val() @@ -617,7 +620,7 @@ function editItemInit() { const $row = $(row); // initialize delete button functionality - $row.find(".list-items-row-button").off("click").on("click", function() { + $row.find(".list-items-row-buttons-delete").off("click").on("click", function() { $row.remove(); initSort(); contentChanged(); @@ -627,24 +630,40 @@ function editItemInit() { const initList = function() { $list.find(".list-data-row").each(function(rowIndex, row) { - // Add the values from the current data row to the template + const id = $(row).data("id"); + + // add the values from the current data row to the template $(row).find(".list-data-row-item").each(function(itemIndex, item) { const $item = $(item), column = $item.data("column"), - value = $item.data("value"); + value = $item.data("value"), + type = $item.data("type"); - $template.find(".list-items-row-input-inner").each(function(inputIndex, input) { - const $input = $(input); + $template.find(".list-items-row-content-inner").each(function(inputIndex, inner) { + const $inner = $(inner); - if ($input.data("column") === column) { - $input.val(value); + if ($inner.data("column") === column) { + if (type === "string") { + $inner.find(".list-input").val(value); + } else if (type === "image" && value !== "") { + $inner.find(".image-link").attr("href", value).addClass("active"); + $inner.find(".image-preview").attr("src", value); + } } }); }); - // Add the populated template to the list of items then clear the template values + // add the populated template to the list of items $template.find(".list-items-row").clone().appendTo($items); - $template.find(".list-items-row-input-inner").val(""); + + // set the id for the list items row + $items.find(".list-items-row").last().data("id", id); + $items.find(".list-items-row").last().find(".list-input").data("id", id); + + // clear the template values + $template.find(".list-input").val(""); + $template.find(".image-link").attr("href", "").removeClass("active"); + $template.find(".image-preview").attr("src", ""); }); initSort(); @@ -740,6 +759,10 @@ function editItemInit() { if (!submitting && changes) { submitting = true; + // find the image and file upload inputs + $imgUploads = $(".image-upload"); + $fileUploads = $(".file-upload"); + // show the loading modal loadingModal("show"); @@ -755,8 +778,26 @@ function editItemInit() { }).always(function(response) { let message = ""; - if ((/^id:[0-9][0-9]*$/).test(response)) { - uploadImage(response.replace(/^id:/, ""), 0); + if (typeof response.id !== "undefined") { + id = response.id; + + // Add the appropriate ids to each list item input + if (Object.keys(response.lists).length) { + Object.keys(response.lists).forEach(function(key) { + $(`#${key}`).find(".list-items").first().find(".list-items-row").each(function(index) { + const listItemId = response.lists[key][index]; + + $(this).data("id", listItemId); + $(this).find(".list-input").data("id", listItemId); + }); + }); + } + + // Add the current row id to each image and file upload input + $(".image-upload, .file-upload").not(".list-input").data("id", response.id); + + // Start uploading images (and then files) + uploadImage(0); } else { loadingModal("hide"); @@ -764,6 +805,8 @@ function editItemInit() { message = `${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, " and $1").replace(/,/g, ", ")} must be unique`; } else if ((/^required:/).test(response)) { message = `${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, " and $1").replace(/,/g, ", ")} must not be empty`; + } else if ((/^invalid-list-model:/).test(response)) { + message = `${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, " and $1").replace(/,/g, ", ")} is not a valid list`; } else { message = `Failed to ${operation} record`; } diff --git a/resources/sass/dashboard.scss b/resources/sass/dashboard.scss index 5396105..1d37f77 100644 --- a/resources/sass/dashboard.scss +++ b/resources/sass/dashboard.scss @@ -917,6 +917,12 @@ form { } } + .container-fluid { + @include media-breakpoint-down(sm) { + padding: 0px; + } + } + .list { margin-bottom: pxrem(15); padding-bottom: pxrem(15); @@ -927,17 +933,37 @@ form { } &-items-row { + position: relative; margin-bottom: pxrem(10); - display: flex; - align-items: center; + padding: pxrem(8) pxrem(8) pxrem(8) pxrem(33); + border-radius: pxrem(6); + background-color: darken($c-input-bg, 2%); + + @include media-breakpoint-up(lg) { + display: flex; + padding: pxrem(5) pxrem(5) pxrem(5) pxrem(30); + align-items: center; + } + + &:not(:last-child) { + @include media-breakpoint-down(lg) { + margin-bottom: pxrem(15); + } + } .sort-icon { - margin-right: pxrem(10); + position: absolute; + top: pxrem(11); + left: pxrem(10); display: inline-block; opacity: 1; transition: opacity 100ms; cursor: grab; + @include media-breakpoint-up(lg) { + top: pxrem(9); + } + &-inner { position: relative; top: pxrem(2); @@ -968,17 +994,29 @@ form { } } - &-input, &-button { - &:not(:first-child) { - margin-left: pxrem(5); + &-content, &-button { + @include media-breakpoint-down(lg) { + width: 100%; } &:not(:last-child) { - margin-right: pxrem(5); + @include media-breakpoint-down(lg) { + margin-bottom: pxrem(10); + } + + @include media-breakpoint-up(lg) { + margin-right: pxrem(5); + } + } + + &:not(:first-child) { + @include media-breakpoint-up(lg) { + margin-left: pxrem(5); + } } } - &-input { + &-content { position: relative; &.wide { @@ -986,29 +1024,52 @@ form { } &-inner { - margin-bottom: 0px; - } + $row-height: pxrem(36); + display: flex; + height: $row-height; + align-items: center; - &-overlay { - overflow: hidden; - position: absolute; - top: 0px; - left: 0px; - width: 100%; - height: 100%; - padding: pxrem(5) pxrem(8); - background-color: $c-input-bg; - white-space: nowrap; - text-overflow: ellipsis; - pointer-events: none; + .list-input { + margin-bottom: 0px; + height: auto; + } + + .image-link { + margin-right: pxrem(10); + display: block; + width: pxrem(50); + height: $row-height; + background-color: $c-input-bg; + + &:not(.active) { + display: none; + } + + .image-preview { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + } + } } } - &-button { - min-width: pxrem(70); - height: $label-height; - border: 1px solid fade-out($c-text, 0.75); - border-radius: pxrem(4); + &-buttons { + @include media-breakpoint-up(lg) { + display: flex; + flex-grow: 1; + justify-content: flex-end; + } + + &-delete { + min-width: pxrem(70); + height: $label-height; + border: 1px solid fade-out($c-text, 0.75); + border-radius: pxrem(4); + background-color: $c-dashboard-delete; + color: $c-text-light; + } } } @@ -1016,6 +1077,11 @@ form { height: $label-height; border: 1px solid fade-out($c-text, 0.75); border-radius: pxrem(4); + + @include media-breakpoint-down(lg) { + margin-top: pxrem(15); + width: 100%; + } } } @@ -1101,6 +1167,7 @@ form { padding: pxrem(5) pxrem(10); border-radius: pxrem(5); text-transform: uppercase; + text-decoration: none; transition: background-color 150ms; cursor: pointer; @@ -1153,6 +1220,10 @@ form { margin-top: pxrem(10); margin-bottom: pxrem(20); } + + @include media-breakpoint-down(sm) { + margin-bottom: 0px; + } } } diff --git a/resources/views/dashboard/pages/edit-item.blade.php b/resources/views/dashboard/pages/edit-item.blade.php index 6148faf..6f8c887 100644 --- a/resources/views/dashboard/pages/edit-item.blade.php +++ b/resources/views/dashboard/pages/edit-item.blade.php @@ -22,7 +22,7 @@ @php $value = $item !== null ? $item[$column['name']] : ''; $type = $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type']; - $ext = array_key_exists('ext', $column) ? $column['ext'] : 'jpg'; + $ext = array_key_exists('ext', $column) ? $column['ext'] : $default_img_ext; @endphp @if($type == 'hidden') @@ -72,6 +72,13 @@ @endphp @endif + @if(gettype($select_title)) + @php + $select_value = $select_value ? 1 : 0; + $select_title = $select_title ? 'true' : 'false'; + @endphp + @endif + @if($select_value === $value) @else @@ -80,9 +87,14 @@ @endforeach @elseif($type == 'list') + @php + $list_model = App\Dashboard::getModel($value['model']); + $list_columns = $list_model::$dashboard_columns; + @endphp +
-
+
@@ -91,22 +103,46 @@
- @foreach($column['columns'] as $list_column) -
- + @foreach($list_columns as $list_column) +
+
+ @if($list_column['type'] == 'string') + + @elseif($list_column['type'] == 'image') + + + @endif +
@endforeach - +
+ +
@if($id != 'new') - @foreach($value as $row) -
- @foreach($column['columns'] as $list_column) -
+ @foreach($value['list'] as $row) +
+ @foreach($list_columns as $list_column) + @if($list_column['type'] == 'string') + @php + $list_column_value = $row[$list_column['name']] + @endphp + @elseif($list_column['type'] == 'image') + @php + $list_column_item = $list_model::find($row['id']); + $list_column_image_ext = array_key_exists('ext', $list_column) ? $list_column['ext'] : $default_img_ext; + $list_column_image_path = $list_model->getUploadsPath('image') . $row['id'] . '-' . $list_column['name'] . '.' . $list_column_image_ext; + $list_column_value = file_exists(public_path($list_column_image_path)) ? $list_column_image_path . '?version=' . $list_column_item->timestamp() : ''; + @endphp + + {{ $list_column_image_path }} + @endif + +
@endforeach
@endforeach @@ -121,14 +157,14 @@ $current_image = "/uploads/$model/img/$id-" . $column['name'] . '.' . $ext; @endphp - + @if(file_exists(base_path() . '/public' . $current_image))
@if(array_key_exists('delete', $column) && $column['delete']) - + Delete Image @endif @@ -139,14 +175,14 @@ $current_file = "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext']; @endphp - + @if(file_exists(base_path() . '/public' . $current_file))
View {{ strtoupper($column['ext']) }} @if(array_key_exists('delete', $column) && $column['delete']) - + Delete {{ strtoupper($column['ext']) }} @endif