Reuse dashboard logic much more, delete images and files when the associated item is deleted, rework dashboard lists so their items can be configured in the $dashboard_columns of their own class, allow dashboard lists to upload images, delete images when dashboard list items are deleted, add a default image extension variable to dashboard model items rather than hard-coding it so it can be reconfigured, improve the dashboard styles some more, and improve the readme (including documenting the new dashboard list update)

This commit is contained in:
Kevin MacMartin 2022-06-14 01:36:21 -04:00
parent ace3c607ca
commit db7deb0fdc
9 changed files with 397 additions and 166 deletions

View file

@ -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;

View file

@ -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']);

View file

@ -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()

View file

@ -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' ]
];
}

View file

@ -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

View file

@ -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' ]
];
```

View file

@ -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 = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> must be unique`;
} else if ((/^required:/).test(response)) {
message = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> must not be empty`;
} else if ((/^invalid-list-model:/).test(response)) {
message = `<strong>${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, "</strong> and <strong>$1").replace(/,/g, "</strong>, <strong>")}</strong> is not a valid list`;
} else {
message = `Failed to <strong>${operation}</strong> record`;
}

View file

@ -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;
}
}
}

View file

@ -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)
<option value="{{ $select_value }}" selected="selected">{{ $select_title }}</option>
@else
@ -80,9 +87,14 @@
@endforeach
</select>
@elseif($type == 'list')
@php
$list_model = App\Dashboard::getModel($value['model']);
$list_columns = $list_model::$dashboard_columns;
@endphp
<div class="list" id="{{ $column['name'] }}">
<div class="list-template">
<div class="list-items-row">
<div class="list-items-row" data-id="new">
<div class="sort-icon" title="Click and drag to reorder">
<div class="sort-icon-inner">
<div class="sort-icon-inner-bar"></div>
@ -91,22 +103,46 @@
</div>
</div>
@foreach($column['columns'] as $list_column)
<div class="list-items-row-input {{ count($column['columns']) == 1 ? 'wide' : '' }}">
<input class="list-items-row-input-inner" data-column="{{ $list_column }}" placeholder="{{ $list_column }}" />
@foreach($list_columns as $list_column)
<div class="list-items-row-content {{ count($list_columns) == 1 ? 'wide' : '' }}">
<div class="list-items-row-content-inner" data-column="{{ $list_column['name'] }}" data-type="{{ $list_column['type'] }}">
@if($list_column['type'] == 'string')
<input class="list-input" placeholder="{{ $list_column['name'] }}" />
@elseif($list_column['type'] == 'image')
<a class="image-link" href="" target="_blank"><img class="image-preview" src="" /></a>
<input class="list-input image-upload" type="file" data-column="{{ $list_column['name'] }}" data-model="{{ $value['model'] }}" />
@endif
</div>
</div>
@endforeach
<button class="list-items-row-button" type="button">Delete</button>
<div class="list-items-row-buttons">
<button class="list-items-row-buttons-delete" type="button">Delete</button>
</div>
</div>
</div>
<div class="list-data">
@if($id != 'new')
@foreach($value as $row)
<div class="list-data-row">
@foreach($column['columns'] as $list_column)
<div class="list-data-row-item" data-column="{{ $list_column }}" data-value="{{ $row[$list_column] }}"></div>
@foreach($value['list'] as $row)
<div class="list-data-row" data-id="{{ $row['id'] }}">
@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
<div class="list-data-row-item" data-type="{{ $list_column['type'] }}" data-column="{{ $list_column['name'] }}" data-value="{{ $list_column_value }}"></div>
@endforeach
</div>
@endforeach
@ -121,14 +157,14 @@
$current_image = "/uploads/$model/img/$id-" . $column['name'] . '.' . $ext;
@endphp
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
<input class="image-upload" type="file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}" />
@if(file_exists(base_path() . '/public' . $current_image))
<div id="current-image-{{ $column['name'] }}">
<img class="current-image" src="{{ $current_image }}?version={{ $item->timestamp() }}" />
@if(array_key_exists('delete', $column) && $column['delete'])
<span class="edit-button delete image" data-name="{{ $column['name'] }}">
<span class="edit-button delete image" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}">
Delete Image
</span>
@endif
@ -139,14 +175,14 @@
$current_file = "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext'];
@endphp
<input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
<input class="file-upload" type="file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}" />
@if(file_exists(base_path() . '/public' . $current_file))
<div id="current-file-{{ $column['name'] }}">
<a class="edit-button view" href="{{ $current_file }}?version={{ $item->timestamp() }}" target="_blank">View {{ strtoupper($column['ext']) }}</a>
@if(array_key_exists('delete', $column) && $column['delete'])
<span class="edit-button delete file" data-name="{{ $column['name'] }}">
<span class="edit-button delete file" data-column="{{ $column['name'] }}" data-model="{{ $model }}" data-id="{{ $id }}">
Delete {{ strtoupper($column['ext']) }}
</span>
@endif