Use a non-static function for getDashboardHeader so we can use getTable and not have to worry about sending the model, pull file and image saving and deletion into the dashboard model so it can be used by seeders and other things, allow filetype to be specified for images so more than just jpg is possible, if the image filetype isn't svg also save a webp version of a given image, add a dashboard model method to get the uploads path so we aren't hard-coding that to one degree or another in a whole bunch of places, allow a max width and/or height to be specified for image uploads to avoid giant images, don't send unnecessary parameters from the back-end to the front-end and through the api back to the back-end, add the ability to show error text to the dashboard alert model, show an error if a file of an incorrect type is uploaded, add webp suport detection logic to the vue-based front end as well as a method that selects the appropriate format of image (falling back on the provided format)

This commit is contained in:
Kevin MacMartin 2021-07-29 16:40:55 -04:00
parent 2a5d046e2c
commit 2a615b75a7
12 changed files with 464 additions and 97 deletions

View file

@ -28,14 +28,14 @@ class DashboardController extends Controller {
return view('dashboard.pages.home'); return view('dashboard.pages.home');
} }
// View Model Data // Page to View Model Data
public function getView($model) public function getView($model)
{ {
$model_class = Dashboard::getModel($model, 'view'); $model_class = Dashboard::getModel($model, 'view');
if ($model_class != null) { if ($model_class != null) {
return view('dashboard.pages.view', [ return view('dashboard.pages.view', [
'heading' => $model_class::getDashboardHeading($model), 'heading' => $model_class->getDashboardHeading(),
'column_headings' => $model_class::getDashboardColumnData('headings'), 'column_headings' => $model_class::getDashboardColumnData('headings'),
'model' => $model, 'model' => $model,
'rows' => $model_class::getDashboardData(), 'rows' => $model_class::getDashboardData(),
@ -46,7 +46,7 @@ class DashboardController extends Controller {
} }
} }
// Edit List of Model Rows // Page to Edit List of Model Rows
public function getEditList($model) public function getEditList($model)
{ {
$model_class = Dashboard::getModel($model, 'edit'); $model_class = Dashboard::getModel($model, 'edit');
@ -55,7 +55,7 @@ class DashboardController extends Controller {
$data = $model_class::getDashboardData(true); $data = $model_class::getDashboardData(true);
return view('dashboard.pages.edit-list', [ return view('dashboard.pages.edit-list', [
'heading' => $model_class::getDashboardHeading($model), 'heading' => $model_class->getDashboardHeading(),
'model' => $model, 'model' => $model,
'rows' => $data['rows'], 'rows' => $data['rows'],
'paramdisplay' => $data['paramdisplay'], 'paramdisplay' => $data['paramdisplay'],
@ -75,7 +75,7 @@ class DashboardController extends Controller {
} }
} }
// Create and Edit Model Item // Page to Create and Edit Model Item
public function getEditItem($model, $id = 'new') public function getEditItem($model, $id = 'new')
{ {
$model_class = Dashboard::getModel($model, 'edit'); $model_class = Dashboard::getModel($model, 'edit');
@ -103,7 +103,7 @@ class DashboardController extends Controller {
} }
return view('dashboard.pages.edit-item', [ return view('dashboard.pages.edit-item', [
'heading' => $model_class::getDashboardHeading($model), 'heading' => $model_class->getDashboardHeading(),
'model' => $model, 'model' => $model,
'id' => $id, 'id' => $id,
'item' => $item, 'item' => $item,
@ -296,15 +296,14 @@ class DashboardController extends Controller {
if (is_null($item)) { if (is_null($item)) {
return 'record-access-fail'; return 'record-access-fail';
} else if (!$item->userCheck()) {
return 'permission-fail';
} else if ($request->hasFile('file')) { } else if ($request->hasFile('file')) {
$directory = base_path() . '/public/uploads/' . $request['model'] . '/img/'; $save_result = $item->saveImage($request['name'], $request->file('file'));
File::makeDirectory($directory, 0755, true, true);
$image = Image::make($request->file('file')); if ($save_result == 'success') {
$image->save($directory . $request['id'] . '-' . $request['name'] . '.jpg');
$item->touch(); $item->touch();
return 'success'; }
return $save_result;
} else { } else {
return 'file-upload-fail'; return 'file-upload-fail';
} }
@ -319,8 +318,7 @@ class DashboardController extends Controller {
$this->validate($request, [ $this->validate($request, [
'id' => 'required', 'id' => 'required',
'model' => 'required', 'model' => 'required',
'name' => 'required', 'name' => 'required'
'ext' => 'required'
]); ]);
$model_class = Dashboard::getModel($request['model'], 'edit'); $model_class = Dashboard::getModel($request['model'], 'edit');
@ -330,14 +328,14 @@ class DashboardController extends Controller {
if (is_null($item)) { if (is_null($item)) {
return 'record-access-fail'; return 'record-access-fail';
} else if (!$item->userCheck()) {
return 'permission-fail';
} else if ($request->hasFile('file')) { } else if ($request->hasFile('file')) {
$directory = base_path() . '/public/uploads/' . $request['model'] . '/files/'; $save_result = $item->saveFile($request['name'], $request->file('file'));
File::makeDirectory($directory, 0755, true, true);
$request->file('file')->move($directory, $request['id'] . '-' . $request['name'] . '.' . $request['ext']); if ($save_result == 'success') {
$item->touch(); $item->touch();
return 'success'; }
return $save_result;
} else { } else {
return 'file-upload-fail'; return 'file-upload-fail';
} }
@ -365,25 +363,17 @@ class DashboardController extends Controller {
return 'permission-fail'; return 'permission-fail';
} }
// delete the row
$item->delete();
// delete associated files if they exist // delete associated files if they exist
foreach ($model_class::$dashboard_columns as $column) { foreach ($model_class::$dashboard_columns as $column) {
if ($column['type'] == 'image') { if ($column['type'] == 'image') {
$image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $column['name'] . '.jpg'; $item->deleteImage($column['name'], false);
if (file_exists($image) && !unlink($image)) {
return 'image-delete-fail';
}
} else if ($column['type'] == 'file') { } else if ($column['type'] == 'file') {
$file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $column['name'] . '.' . $column['ext']; $item->deleteFile($column['name'], false);
}
}
if (file_exists($file) && !unlink($file)) { // delete the row
return 'file-delete-fail'; $item->delete();
}
}
}
// update the order of the remaining rows if $dashboard_reorder is true // update the order of the remaining rows if $dashboard_reorder is true
if ($model_class::$dashboard_reorder) { if ($model_class::$dashboard_reorder) {
@ -412,20 +402,13 @@ class DashboardController extends Controller {
$model_class = Dashboard::getModel($request['model'], 'edit'); $model_class = Dashboard::getModel($request['model'], 'edit');
if ($model_class != null) { if ($model_class != null) {
$image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $request['name'] . '.jpg';
$item = $model_class::find($request['id']); $item = $model_class::find($request['id']);
if (is_null($item)) { if (is_null($item)) {
return 'record-access-fail'; return 'record-access-fail';
} else if (!$item->userCheck()) {
return 'permission-fail';
} else if (!file_exists($image)) {
return 'image-not-exists-fail';
} else if (!unlink($image)) {
return 'image-delete-fail';
} }
return 'success'; return $item->deleteImage($request['name'], true);
} else { } else {
return 'model-access-fail'; return 'model-access-fail';
} }
@ -437,27 +420,19 @@ class DashboardController extends Controller {
$this->validate($request, [ $this->validate($request, [
'id' => 'required', 'id' => 'required',
'model' => 'required', 'model' => 'required',
'name' => 'required', 'name' => 'required'
'ext' => 'required'
]); ]);
$model_class = Dashboard::getModel($request['model'], 'edit'); $model_class = Dashboard::getModel($request['model'], 'edit');
if ($model_class != null) { if ($model_class != null) {
$file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $request['name'] . '.' . $request['ext'];
$item = $model_class::find($request['id']); $item = $model_class::find($request['id']);
if (is_null($item)) { if (is_null($item)) {
return 'record-access-fail'; return 'record-access-fail';
} else if (!$item->userCheck()) {
return 'permission-fail';
} else if (!file_exists($file)) {
return 'file-not-exists-fail';
} else if (!unlink($file)) {
return 'file-delete-fail';
} }
return 'success'; return $item->deleteFile($request['name'], true);
} else { } else {
return 'model-access-fail'; return 'model-access-fail';
} }

View file

@ -49,8 +49,8 @@ class Blog extends DashboardModel
$blog_entry['tags'] = $tags; $blog_entry['tags'] = $tags;
// Add the header image if one exists // Add the header image if one exists
$header_image_path = '/uploads/blog/img/' . $blog_entry->id . '-header-image.jpg'; $header_image_path = $blog_entry->getUploadsPath('image') . $blog_entry->id . '-header-image.jpg';
$blog_entry['headerimage'] = file_exists(base_path() . '/public' . $header_image_path) ? $header_image_path . '?version=' . $blog_entry->timestamp() : ''; $blog_entry['headerimage'] = file_exists(public_path($header_image_path)) ? $header_image_path . '?version=' . $blog_entry->timestamp() : '';
// Add the processed blog entry to the array // Add the processed blog entry to the array
array_push($blog_entries, $blog_entry); array_push($blog_entries, $blog_entry);

View file

@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Auth; use Auth;
use File;
use Image;
use App\Traits\Timestamp; use App\Traits\Timestamp;
class DashboardModel extends Model class DashboardModel extends Model
@ -120,13 +122,316 @@ class DashboardModel extends Model
* *
* @return string * @return string
*/ */
public static function getDashboardHeading($model) public function getDashboardHeading()
{ {
return static::$dashboard_heading == null ? ucfirst($model) : static::$dashboard_heading; return static::$dashboard_heading == null ? ucfirst($this->getTable()) : static::$dashboard_heading;
} }
/** /**
* Returns an array of column 'headings' or 'names' * Return the upload path for a given type
*
* @return boolean
*/
public function getUploadsPath($type)
{
if ($type == 'image') {
return '/uploads/' . $this->getTable() . '/img/';
} else if ($type == 'file') {
return '/uploads/' . $this->getTable() . '/files/';
}
}
/**
* Save an image
*
* @return boolean
*/
public function saveImage($name, $file)
{
// Fail if the user doesn't have permission
if (!$this->userCheck()) {
return 'permission-fail';
}
$max_width = 0;
$max_height = 0;
$main_ext = 'jpg';
// Retrieve the column
$column = static::getColumn($name);
// Return an error if no column is found
if ($column == null) {
return 'no-such-column-fail';
}
// Update the extension if it's been configured
if (array_key_exists('ext', $column)) {
$main_ext = $column['ext'];
}
// Create the directory if it doesn't exist
$directory = public_path($this->getUploadsPath('image'));
File::makeDirectory($directory, 0755, true, true);
// Set the base file path (including the file name but not the extension)
$base_filename = $directory . $this->id . '-' . $name . '.';
if ($main_ext == 'svg') {
// Save the image provided it's an SVG
if (gettype($file) == 'string') {
if (!preg_match('/\.svg$/i', $file)) {
return 'incorrect-format-fail';
}
copy($file, $base_filename . $main_ext);
} else {
if ($file->extension() != 'svg') {
return 'incorrect-format-fail';
}
$file->move($directory, $base_filename . $main_ext);
}
} else {
// Update the maximum width if it's been configured
if (array_key_exists('max_width', $column)) {
$max_width = $column['max_width'];
}
// Update the maximum height if it's been configured
if (array_key_exists('max_height', $column)) {
$max_height = $column['max_height'];
}
$image = Image::make($file);
if ($max_width > 0 || $max_height > 0) {
$width = $image->width();
$height = $image->height();
$new_width = null;
$new_height = null;
if ($max_width > 0 && $max_height > 0) {
if ($width > $max_width || $height > $max_height) {
$new_width = $max_width;
$new_height = ($new_width / $width) * $height;
if ($new_height > $max_height) {
$new_width = ($max_height / $height) * $width;
}
}
} else if ($max_width > 0) {
if ($width > $max_width) {
$new_width = $max_width;
}
} else if ($height > $max_height) {
$new_height = $max_height;
}
if (!is_null($new_width) || !is_null($new_height)) {
$image->resize($new_width, $new_height, function($constraint) {
$constraint->aspectRatio();
});
}
}
$image->save($base_filename . $main_ext);
$image->save($base_filename . 'webp');
}
return 'success';
}
/*
* Delete an image
*
* @return string
*/
public function deleteImage($name, $not_exist_fail)
{
// Fail if the user doesn't have permission
if (!$this->userCheck()) {
return 'permission-fail';
}
// Set up our variables
$main_ext = 'jpg';
$extensions = [];
// Retrieve the column
$column = static::getColumn($name);
// Return an error if no column is found
if ($column == null) {
return 'no-such-column-fail';
}
// Update the extension if it's been configured
if (array_key_exists('ext', $column)) {
$main_ext = $column['ext'];
}
// Build the set of extensions to delete
array_push($extensions, $main_ext);
// If the image extension isn't svg also delete the webp
if ($main_ext != 'svg') {
array_push($extensions, 'webp');
}
// Delete each image
foreach ($extensions as $ext) {
// Get the full path of the image
$image = public_path($this->getUploadsPath('image') . $this->id . '-' . $name . '.' . $ext);
// Try to delete the image
if (file_exists($image)) {
if (!unlink($image)) {
return 'image-delete-fail';
}
} else if ($not_exist_fail) {
return 'image-not-exists-fail';
}
}
// Success
return 'success';
}
/**
* Save a file
*
* @return boolean
*/
public function saveFile($name, $file)
{
// Fail if the user doesn't have permission
if (!$this->userCheck()) {
return 'permission-fail';
}
// Retrieve the column
$column = static::getColumn($name);
// Return an error if no column is found
if ($column == null) {
return 'no-such-column-fail';
}
// Fail if an ext hasn't been declared
if (!array_key_exists('ext', $column)) {
return 'no-configured-extension-fail';
}
// Store the extension
$ext = $column['ext'];
// Create the directory if it doesn't exist
$directory = public_path($this->getUploadsPath('file'));
File::makeDirectory($directory, 0755, true, true);
// Save the file provided it's the correct extension
if (gettype($file) == 'string') {
if (!preg_match("/\.$ext/i", $file)) {
return 'incorrect-format-fail';
}
copy($file, $base_filename . $main_ext);
} else {
if ($file->extension() != $ext) {
return 'incorrect-format-fail';
}
$file->move($directory, $this->id . '-' . $name . '.' . $ext);
}
// Success
return 'success';
}
/*
* Delete a file
*
* @return string
*/
public function deleteFile($name, $not_exist_fail)
{
// Fail if the user doesn't have permission
if (!$this->userCheck()) {
return 'permission-fail';
}
// Retrieve the column
$column = static::getColumn($name);
// Return an error if no column is found
if ($column == null) {
return 'no-such-column-fail';
}
// Fail if an ext hasn't been declared
if (!array_key_exists('ext', $column)) {
return 'no-configured-extension-fail';
}
// Store the extension
$ext = $column['ext'];
// Get the full path of the file
$file = public_path($this->getUploadsPath('file') . $this->id . '-' . $name . '.' . $ext);
// Try to delete the file
if (file_exists($file)) {
if (!unlink($file)) {
return 'file-delete-fail';
}
} else if ($not_exist_fail) {
return 'file-not-exists-fail';
}
// Success
return 'success';
}
/**
* Determine whether a user column exists and whether it matches the current user if it does
*
* @return boolean
*/
public function userCheck()
{
$user_check = true;
foreach (static::$dashboard_columns as $column) {
if (array_key_exists('type', $column) && $column['type'] == 'user') {
if ($this->{$column['name']} != Auth::id()) {
$user_check = false;
}
break;
}
}
return $user_check;
}
/**
* Get the file extension for an image
*
* @return string
*/
public static function getColumn($name)
{
foreach (static::$dashboard_columns as $column) {
if ($column['name'] == $name) {
return $column;
}
}
return null;
}
/**
* Return an array of column 'headings' or 'names'
* *
* @return array * @return array
*/ */
@ -152,7 +457,7 @@ class DashboardModel extends Model
} }
/** /**
* Performs a search against the columns in $dashboard_display * Perform a search against the columns in $dashboard_display
* *
* @return array * @return array
*/ */
@ -192,7 +497,7 @@ class DashboardModel extends Model
} }
/** /**
* Returns data for the dashboard * Return data for the dashboard
* *
* @return array * @return array
*/ */
@ -250,7 +555,7 @@ class DashboardModel extends Model
} }
/** /**
* Retrieves the current query string containing valid query parameters * Retrieve the current query string containing valid query parameters
* *
* @return string * @return string
*/ */
@ -275,25 +580,4 @@ class DashboardModel extends Model
return $string; return $string;
} }
/**
* Determines whether a user column exists and whether it matches the current user if it does
*
* @return boolean
*/
public function userCheck() {
$user_check = true;
foreach (static::$dashboard_columns as $column) {
if (array_key_exists('type', $column) && $column['type'] == 'user') {
if ($this->{$column['name']} != Auth::id()) {
$user_check = false;
}
break;
}
}
return $user_check;
}
} }

2
gulpfile.js vendored
View file

@ -265,7 +265,7 @@ gulp.task("watch", () => {
}); });
gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ], gulp.series(browserSyncReload)); gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ], gulp.series(browserSyncReload));
gulp.watch([ "resources/js/**/app.js", "resources/js/mixins/**/*.js", "resources/components/**/*.vue" ], gulp.series("js-public", browserSyncReload)); gulp.watch([ "resources/js/**/app.js", "resources/js/mixins/**/*.js", "resources/js/imports/**/*.js", "resources/components/**/*.vue" ], gulp.series("js-public", browserSyncReload));
gulp.watch("resources/js/**/dashboard.js", gulp.series("js-dashboard", browserSyncReload)); gulp.watch("resources/js/**/dashboard.js", gulp.series("js-dashboard", browserSyncReload));
gulp.watch("resources/sass/**/*.scss", gulp.parallel("sass-public", "sass-dashboard", "sass-error")); gulp.watch("resources/sass/**/*.scss", gulp.parallel("sass-public", "sass-dashboard", "sass-error"));
}); });

View file

@ -218,7 +218,9 @@ Models with their `$dashboard_type` set to `edit` also use:
* `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded) * `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded)
* `name`: (required by `file` and `image`) Used along with the record id to determine the filename * `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 * `delete`: (optional for `file` and `image`) Enables a delete button for the upload when set to true
* `ext`: (required by `file`) Configures the file extension of the upload * `ext`: (required by `file` and optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`)
* `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)
An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`: An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`:
@ -238,7 +240,7 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ
[ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ], [ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
[ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ], [ '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', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
]; ];
``` ```

View file

@ -12,7 +12,7 @@
<div <div
v-if="entry.headerimage !== ''" v-if="entry.headerimage !== ''"
class="blog-entry-header-image" class="blog-entry-header-image"
:style="{ backgroundImage: 'url(' + entry.headerimage + ')' }"> :style="{ backgroundImage: 'url(' + imageType(entry.headerimage) + ')' }">
</div> </div>
<div class="blog-entry-content"> <div class="blog-entry-content">

23
resources/js/app.js vendored
View file

@ -15,6 +15,15 @@ Vue.use(Vuex);
// CSRF prevention header // CSRF prevention header
Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken; Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken;
// Import local javascript
import SupportsWebP from "imports/supports-webp.js";
// Import global mixins
import ImageType from "mixins/image-type.js";
// Register global mixins
Vue.mixin(ImageType);
// Import global components // Import global components
import NavSection from "sections/nav.vue"; import NavSection from "sections/nav.vue";
import FooterSection from "sections/footer.vue"; import FooterSection from "sections/footer.vue";
@ -61,7 +70,8 @@ const store = new Vuex.Store({
appLang: env.appLang, appLang: env.appLang,
appDefaultLang: env.appDefaultLang, appDefaultLang: env.appDefaultLang,
firstLoad: true, firstLoad: true,
lastPath: "" lastPath: "",
supportsWebP: null
}, },
getters: { getters: {
@ -83,6 +93,10 @@ const store = new Vuex.Store({
getLastPath: state => { getLastPath: state => {
return state.lastPath; return state.lastPath;
},
getSupportsWebP: state => {
return state.supportsWebP;
} }
}, },
@ -98,6 +112,10 @@ const store = new Vuex.Store({
setLastPath(state, value) { setLastPath(state, value) {
state.lastPath = value; state.lastPath = value;
},
setSupportsWebP(state, value) {
state.supportsWebP = value;
} }
}, },
@ -106,6 +124,9 @@ const store = new Vuex.Store({
} }
}); });
// Detect webp support
SupportsWebP.detect(store);
// Sync vue-router-sync with vuex store // Sync vue-router-sync with vuex store
sync(store, router); sync(store, router);

View file

@ -4,6 +4,19 @@ const fadeTime = 250;
// declare a reverse function for jquery // declare a reverse function for jquery
jQuery.fn.reverse = [].reverse; jQuery.fn.reverse = [].reverse;
// extends an error message with additional text
function getErrorText(message, response) {
let errorText = message;
switch (response) {
case "incorrect-format-fail":
errorText += ": Incorrect file format";
break;
}
return errorText;
}
// show or hide the loading modal // show or hide the loading modal
function loadingModal(action) { function loadingModal(action) {
const $loadingModal = $("#loading-modal"); const $loadingModal = $("#loading-modal");
@ -429,7 +442,6 @@ function editItemInit() {
file.append("name", $(fileUpload).attr("name")); file.append("name", $(fileUpload).attr("name"));
file.append("id", row_id); file.append("id", row_id);
file.append("model", model); file.append("model", model);
file.append("ext", $(fileUpload).data("ext"));
$.ajax({ $.ajax({
type: "POST", type: "POST",
@ -444,7 +456,7 @@ function editItemInit() {
} else { } else {
loadingModal("hide"); loadingModal("hide");
showAlert("Failed to upload file", function() { showAlert(getErrorText("Failed to upload file", response), function() {
submitting = false; submitting = false;
}); });
} }
@ -491,7 +503,7 @@ function editItemInit() {
} else { } else {
loadingModal("hide"); loadingModal("hide");
showAlert("Failed to upload image", function() { showAlert(getErrorText("Failed to upload image", response), function() {
submitting = false; submitting = false;
}); });
} }

48
resources/js/imports/supports-webp.js vendored Normal file
View file

@ -0,0 +1,48 @@
export default {
detect: (store) => {
const webpTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA=="
};
const results = {
lossy: null,
lossless: null,
alpha: null
};
const getResultsValues = () => {
return Object.keys(results).map((feature) => {
return results[feature];
});
};
const callback = (feature, result) => {
results[feature] = result;
if (getResultsValues().indexOf(null) === -1) {
store.commit("setSupportsWebP", getResultsValues().indexOf(false) === -1);
console.log(store.getters.getSupportsWebP);
}
};
const checkFeature = (feature) => {
const img = new Image();
img.onload = function() {
callback(feature, img.width > 0 && img.height > 0);
};
img.onerror = function() {
callback(feature, false);
};
img.src = "data:image/webp;base64," + webpTestImages[feature];
};
Object.keys(webpTestImages).forEach((feature) => {
checkFeature(feature);
});
}
};

13
resources/js/mixins/image-type.js vendored Normal file
View file

@ -0,0 +1,13 @@
export default {
methods: {
imageType(image) {
if (this.$store.getters.getSupportsWebP === true) {
return image.replace(/\.(png|jpg)/, ".webp");
} else if (this.$store.getters.getSupportsWebP === false) {
return image;
} else {
return "";
}
}
}
};

View file

@ -21,6 +21,7 @@
<div class="row"> <div class="row">
@set('value', $item !== null ? $item[$column['name']] : '') @set('value', $item !== null ? $item[$column['name']] : '')
@set('type', $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type']) @set('type', $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type'])
@set('ext', array_key_exists('ext', $column) ? $column['ext'] : 'jpg')
@if($type == 'hidden') @if($type == 'hidden')
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" /> <input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" />
@ -28,7 +29,17 @@
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" /> <input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" />
@elseif($type != 'display' || $id != 'new') @elseif($type != 'display' || $id != 'new')
<div class="col-12 col-md-4 col-lg-3"> <div class="col-12 col-md-4 col-lg-3">
<label for="{{ $column['name'] }}">{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}:</label> <label for="{{ $column['name'] }}">
{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}
@if($column['type'] == 'image')
@if($ext == 'svg')
(SVG)
@endif
@elseif($column['type'] == 'file')
({{ strtoupper($ext) }})
@endif
</label>
</div> </div>
<div class="col-12 col-md-8 col-lg-9"> <div class="col-12 col-md-8 col-lg-9">
@ -100,7 +111,7 @@
<button class="list-add-button" type="button">Add</button> <button class="list-add-button" type="button">Add</button>
</div> </div>
@elseif($type == 'image') @elseif($type == 'image')
@set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg') @set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.' . $ext)
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" /> <input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
@if(file_exists(base_path() . '/public' . $current_image)) @if(file_exists(base_path() . '/public' . $current_image))
@ -116,14 +127,14 @@
@endif @endif
@elseif($type == 'file') @elseif($type == 'file')
@set('current_file', "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext']) @set('current_file', "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext'])
<input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" data-ext="{{ $column['ext'] }}" /> <input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
@if(file_exists(base_path() . '/public' . $current_file)) @if(file_exists(base_path() . '/public' . $current_file))
<div id="current-file-{{ $column['name'] }}"> <div id="current-file-{{ $column['name'] }}">
<a class="edit-button view" href="{{ $current_file }}?version={{ $item->timestamp() }}" target="_blank">View {{ strtoupper($column['ext']) }}</a> <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']) @if(array_key_exists('delete', $column) && $column['delete'])
<span class="edit-button delete file" data-name="{{ $column['name'] }}" data-ext="{{ $column['ext'] }}"> <span class="edit-button delete file" data-name="{{ $column['name'] }}">
Delete {{ strtoupper($column['ext']) }} Delete {{ strtoupper($column['ext']) }}
</span> </span>
@endif @endif

View file

@ -6,11 +6,12 @@
<script> <script>
var env = { var env = {
appName: "{{ env('APP_NAME') }}", appName: "{!! env('APP_NAME') !!}",
appDesc: "{{ env('APP_DESC') }}", appDesc: "{!! env('APP_DESC') !!}",
appLang: "{{ Language::getSessionLanguage() }}", appLang: "{{ Language::getSessionLanguage() }}",
appDefaultLang: "{{ env('DEFAULT_LANGUAGE') }}", appDefaultLang: "{{ env('DEFAULT_LANGUAGE') }}",
apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}", apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}",
currentUrl: "{{ Request::url() }}",
csrfToken: "{{ csrf_token() }}", csrfToken: "{{ csrf_token() }}",
debug: {{ Config::get('app.debug') ? 'true' : 'false' }} debug: {{ Config::get('app.debug') ? 'true' : 'false' }}
}; };