first upload all files
This commit is contained in:
31
Modules/Media/Admin/MediaTable.php
Normal file
31
Modules/Media/Admin/MediaTable.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Admin;
|
||||
|
||||
use Modules\Admin\Ui\AdminTable;
|
||||
|
||||
class MediaTable extends AdminTable
|
||||
{
|
||||
/**
|
||||
* Raw columns that will not be escaped.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rawColumns = ['action'];
|
||||
|
||||
/**
|
||||
* Make table response for the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function make()
|
||||
{
|
||||
return $this->newTable()
|
||||
->editColumn('thumbnail', function ($file) {
|
||||
return view('media::admin.media.partials.table.thumbnail', compact('file'));
|
||||
})
|
||||
->addColumn('action', function ($file) {
|
||||
return view('media::admin.media.partials.table.action', compact('file'));
|
||||
});
|
||||
}
|
||||
}
|
||||
22
Modules/Media/Config/assets.php
Normal file
22
Modules/Media/Config/assets.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which assets will be available through the asset manager
|
||||
| These assets are registered on the asset manager.
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'all_assets' => [
|
||||
'admin.media.css' => ['module' => 'media:admin/css/media.css'],
|
||||
'admin.media.js' => ['module' => 'media:admin/js/media.js'],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which default assets will always be included in all pages
|
||||
| through the asset pipeline.
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'required_assets' => [],
|
||||
];
|
||||
9
Modules/Media/Config/permissions.php
Normal file
9
Modules/Media/Config/permissions.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'admin.media' => [
|
||||
'index' => 'media::permissions.index',
|
||||
'create' => 'media::permissions.create',
|
||||
'destroy' => 'media::permissions.destroy',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFilesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('user_id')->unsigned()->index();
|
||||
$table->string('filename')->index();
|
||||
$table->string('disk');
|
||||
$table->string('path');
|
||||
$table->string('extension');
|
||||
$table->string('mime');
|
||||
$table->string('size');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('files');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateEntityFilesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('entity_files', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('file_id')->unsigned()->index();
|
||||
$table->morphs('entity');
|
||||
$table->string('zone')->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('file_id')->references('id')->on('files')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('entity_files');
|
||||
}
|
||||
}
|
||||
67
Modules/Media/Eloquent/HasMedia.php
Normal file
67
Modules/Media/Eloquent/HasMedia.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Eloquent;
|
||||
|
||||
use Modules\Media\Entities\File;
|
||||
|
||||
trait HasMedia
|
||||
{
|
||||
/**
|
||||
* The "booting" method of the trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasMedia()
|
||||
{
|
||||
static::saved(function ($entity) {
|
||||
$entity->syncFiles(request('files', []));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the files for the entity.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
return $this->morphToMany(File::class, 'entity', 'entity_files')
|
||||
->withPivot(['id', 'zone'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter files by zone.
|
||||
*
|
||||
* @param string $zone
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function filterFiles($zone)
|
||||
{
|
||||
return $this->files()->wherePivot('zone', $zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync files for the entity.
|
||||
*
|
||||
* @param array $files
|
||||
*/
|
||||
public function syncFiles($files = [])
|
||||
{
|
||||
$entityType = get_class($this);
|
||||
|
||||
foreach ($files as $zone => $fileIds) {
|
||||
$syncList = [];
|
||||
|
||||
foreach (array_wrap($fileIds) as $fileId) {
|
||||
if (! empty($fileId)) {
|
||||
$syncList[$fileId]['zone'] = $zone;
|
||||
$syncList[$fileId]['entity_type'] = $entityType;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filterFiles($zone)->detach();
|
||||
$this->filterFiles($zone)->attach($syncList);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Modules/Media/Entities/File.php
Normal file
108
Modules/Media/Entities/File.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
use Modules\Media\IconResolver;
|
||||
use Modules\User\Entities\User;
|
||||
use Modules\Media\Admin\MediaTable;
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class File extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be visible in serialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $visible = ['id', 'filename', 'path'];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::deleting(function ($file) {
|
||||
Storage::disk($file->disk)->delete($file->getRawOriginal('path'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that uploaded the file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function uploader()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPathAttribute($path)
|
||||
{
|
||||
if (! is_null($path)) {
|
||||
return Storage::disk($this->disk)->url($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file's real path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function realPath()
|
||||
{
|
||||
if (! is_null($this->attributes['path'])) {
|
||||
return Storage::disk($this->disk)->path($this->attributes['path']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file type is image.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isImage()
|
||||
{
|
||||
return strtok($this->mime, '/') === 'image';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's icon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function icon()
|
||||
{
|
||||
return IconResolver::resolve($this->mime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table data for the resource
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function table($request)
|
||||
{
|
||||
$query = $this->newQuery()
|
||||
->when(! is_null($request->type) && $request->type !== 'null', function ($query) use ($request) {
|
||||
$query->where('mime', 'LIKE', "{$request->type}/%");
|
||||
});
|
||||
|
||||
return new MediaTable($query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Http\Controllers\Admin;
|
||||
|
||||
class FileManagerController
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource..
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$type = request('type');
|
||||
|
||||
return view('media::admin.file_manager.index', compact('type'));
|
||||
}
|
||||
}
|
||||
67
Modules/Media/Http/Controllers/Admin/MediaController.php
Normal file
67
Modules/Media/Http/Controllers/Admin/MediaController.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Http\Controllers\Admin;
|
||||
|
||||
use Modules\Media\Entities\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Admin\Traits\HasCrudActions;
|
||||
use Modules\Media\Http\Requests\UploadMediaRequest;
|
||||
|
||||
class MediaController
|
||||
{
|
||||
use HasCrudActions;
|
||||
|
||||
/**
|
||||
* Model for the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = File::class;
|
||||
|
||||
/**
|
||||
* Label of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = 'media::media.media';
|
||||
|
||||
/**
|
||||
* View path of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $viewPath = 'media::admin.media';
|
||||
|
||||
/**
|
||||
* Store a newly created media in storage.
|
||||
*
|
||||
* @param \Modules\Media\Http\Requests\UploadMediaRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(UploadMediaRequest $request)
|
||||
{
|
||||
$file = $request->file('file');
|
||||
$path = Storage::putFile('media', $file);
|
||||
|
||||
return File::create([
|
||||
'user_id' => auth()->id(),
|
||||
'disk' => config('filesystems.default'),
|
||||
'filename' => $file->getClientOriginalName(),
|
||||
'path' => $path,
|
||||
'extension' => $file->guessClientExtension() ?? '',
|
||||
'mime' => $file->getClientMimeType(),
|
||||
'size' => $file->getSize(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resources from storage.
|
||||
*
|
||||
* @param string $ids
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($ids)
|
||||
{
|
||||
File::find(explode(',', $ids))->each->delete();
|
||||
}
|
||||
}
|
||||
20
Modules/Media/Http/Requests/UploadMediaRequest.php
Normal file
20
Modules/Media/Http/Requests/UploadMediaRequest.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Http\Requests;
|
||||
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
|
||||
class UploadMediaRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'file' => 'file',
|
||||
];
|
||||
}
|
||||
}
|
||||
50
Modules/Media/IconResolver.php
Normal file
50
Modules/Media/IconResolver.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media;
|
||||
|
||||
class IconResolver
|
||||
{
|
||||
/**
|
||||
* The icon lookup table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $icons = [
|
||||
'image' => 'fa-picture-o',
|
||||
'audio' => 'fa-file-video-o',
|
||||
'video' => 'fa-file-video-o',
|
||||
'pdf' => 'fa-file-pdf-o',
|
||||
'zip' => 'fa-file-archive-o',
|
||||
'vnd.rar' => 'fa-file-archive-o',
|
||||
'x-tar' => 'fa-file-archive-o',
|
||||
'gzip' => 'fa-file-archive-o',
|
||||
'x-bzip' => 'fa-file-archive-o',
|
||||
'x-7z-compressed' => 'fa-file-archive-o',
|
||||
'file' => 'fa-file-o',
|
||||
];
|
||||
|
||||
/**
|
||||
* Resolve icon for the given mime type.
|
||||
*
|
||||
* @param string $mime
|
||||
* @return string
|
||||
*/
|
||||
public static function resolve($mime)
|
||||
{
|
||||
if (is_null($mime)) {
|
||||
return static::$icons['file'];
|
||||
}
|
||||
|
||||
list($firstHint, $secondHint) = explode('/', $mime);
|
||||
|
||||
if (array_key_exists($firstHint, static::$icons)) {
|
||||
return static::$icons[$firstHint];
|
||||
}
|
||||
|
||||
if (array_key_exists($secondHint, static::$icons)) {
|
||||
return static::$icons[$secondHint];
|
||||
}
|
||||
|
||||
return static::$icons['file'];
|
||||
}
|
||||
}
|
||||
25
Modules/Media/Providers/MediaServiceProvider.php
Normal file
25
Modules/Media/Providers/MediaServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Modules\Support\Traits\AddsAsset;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Admin\Http\ViewComposers\AssetsComposer;
|
||||
|
||||
class MediaServiceProvider extends ServiceProvider
|
||||
{
|
||||
use AddsAsset;
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
View::composer('media::admin.file_manager.index', AssetsComposer::class);
|
||||
|
||||
$this->addAdminAssets('admin.(media|file_manager).(index|edit)', ['admin.media.css', 'admin.media.js']);
|
||||
}
|
||||
}
|
||||
79
Modules/Media/Resources/assets/admin/js/ImagePicker.js
Normal file
79
Modules/Media/Resources/assets/admin/js/ImagePicker.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import MediaPicker from './MediaPicker';
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
$('.image-picker').on('click', (e) => {
|
||||
this.pickImage(e);
|
||||
});
|
||||
|
||||
this.sortable();
|
||||
this.removeImageEventListener();
|
||||
}
|
||||
|
||||
pickImage(e) {
|
||||
let inputName = e.currentTarget.dataset.inputName;
|
||||
let multiple = e.currentTarget.hasAttribute('data-multiple');
|
||||
|
||||
let picker = new MediaPicker({ type: 'image', multiple });
|
||||
|
||||
picker.on('select', (file) => {
|
||||
this.addImage(inputName, file, multiple, e.currentTarget);
|
||||
});
|
||||
}
|
||||
|
||||
addImage(inputName, file, multiple, target) {
|
||||
let html = this.getTemplate(inputName, file);
|
||||
|
||||
if (multiple) {
|
||||
let multipleImagesWrapper = $(target).next('.multiple-images');
|
||||
|
||||
multipleImagesWrapper.find('.image-holder.placeholder').remove();
|
||||
multipleImagesWrapper.find('.image-list').append(html);
|
||||
} else {
|
||||
$(target).siblings('.single-image').html(html);
|
||||
}
|
||||
}
|
||||
|
||||
getTemplate(inputName, file) {
|
||||
return $(`
|
||||
<div class="image-holder">
|
||||
<img src="${file.path}">
|
||||
<button type="button" class="btn remove-image"></button>
|
||||
<input type="hidden" name="${inputName}" value="${file.id}">
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
sortable() {
|
||||
let imageList = $('.image-list');
|
||||
|
||||
if (imageList.length > 0) {
|
||||
Sortable.create(imageList[0], { animation: 150 });
|
||||
}
|
||||
}
|
||||
|
||||
removeImageEventListener() {
|
||||
$('.image-holder-wrapper').on('click', '.remove-image', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let imageHolderWrapper = $(e.currentTarget).closest('.image-holder-wrapper');
|
||||
|
||||
if (imageHolderWrapper.find('.image-holder').length === 1) {
|
||||
imageHolderWrapper.html(
|
||||
this.getImagePlaceholder(e.currentTarget.dataset.inputName)
|
||||
);
|
||||
}
|
||||
|
||||
$(e.currentTarget).parent().remove();
|
||||
});
|
||||
}
|
||||
|
||||
getImagePlaceholder(inputName) {
|
||||
return `
|
||||
<div class="image-holder placeholder cursor-auto">
|
||||
<i class="fa fa-picture-o"></i>
|
||||
<input type="hidden" name="${inputName}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
120
Modules/Media/Resources/assets/admin/js/MediaPicker.js
Normal file
120
Modules/Media/Resources/assets/admin/js/MediaPicker.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import '../sass/media-picker.scss';
|
||||
|
||||
export default class {
|
||||
constructor(options) {
|
||||
this.options = _.merge({
|
||||
type: null,
|
||||
multiple: false,
|
||||
route: 'admin.file_manager.index',
|
||||
title: trans('media::media.file_manager.title'),
|
||||
message: trans('media::messages.image_has_been_added'),
|
||||
}, options);
|
||||
|
||||
this.events = {};
|
||||
this.frame = this.getFrame();
|
||||
|
||||
this.appendModalToBody();
|
||||
this.openFrame();
|
||||
}
|
||||
|
||||
on(event, handler) {
|
||||
this.events[event] = handler;
|
||||
}
|
||||
|
||||
getFrame() {
|
||||
let src = route(this.options.route, {
|
||||
type: this.options.type,
|
||||
multiple: this.options.multiple,
|
||||
});
|
||||
|
||||
return $(`<iframe class="file-manager-iframe" frameborder="0" src="${src}"></iframe>`);
|
||||
}
|
||||
|
||||
appendModalToBody() {
|
||||
if ($('.media-picker-modal').length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').append(this.getModal());
|
||||
}
|
||||
|
||||
openFrame() {
|
||||
this.showModal();
|
||||
|
||||
this.frame.on('load', () => {
|
||||
this.selectMedia();
|
||||
});
|
||||
}
|
||||
|
||||
showModal() {
|
||||
let modal = $('.media-picker-modal').modal('show');
|
||||
|
||||
this.setFrameHeight();
|
||||
this.setModalTitle(modal);
|
||||
this.setModalBody(modal);
|
||||
this.closeModalOnEsc(modal);
|
||||
}
|
||||
|
||||
setFrameHeight() {
|
||||
this.frame.css('height', window.innerHeight * 0.8);
|
||||
}
|
||||
|
||||
setModalTitle(modal) {
|
||||
modal.find('.modal-title').text(this.options.title);
|
||||
}
|
||||
|
||||
setModalBody(modal) {
|
||||
modal.find('.modal-body').html(this.frame);
|
||||
}
|
||||
|
||||
closeModalOnEsc(modal) {
|
||||
$(document).on('keydown', (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
modal.modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
this.frame.on('load keydown', () => {
|
||||
this.frame.contents().on('keydown', (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
modal.modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selectMedia() {
|
||||
this.frame.contents().find('.table').on('click', '.select-media', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.events['select'](e.currentTarget.dataset);
|
||||
|
||||
if (this.options.multiple) {
|
||||
if (this.options.message) {
|
||||
notify('success', this.options.message, { context: this.frame.contents() });
|
||||
}
|
||||
} else {
|
||||
$('.media-picker-modal').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getModal() {
|
||||
return `
|
||||
<div class="media-picker-modal modal fade" role="dialog">
|
||||
<div class="modal-dialog clearfix">
|
||||
<div class="modal-content col-md-10 col-sm-11 clearfix">
|
||||
<div class="row">
|
||||
<div class="modal-header">
|
||||
<a type="button" class="close" data-dismiss="modal">×</a>
|
||||
<h4 class="modal-title"></h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
34
Modules/Media/Resources/assets/admin/js/Uploader.js
Normal file
34
Modules/Media/Resources/assets/admin/js/Uploader.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import Dropzone from 'dropzone';
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
this.dropzone = new Dropzone('.dropzone', {
|
||||
url: route('admin.media.store'),
|
||||
autoProcessQueue: true,
|
||||
maxFilesize: FleetCart.maxFileSize,
|
||||
});
|
||||
|
||||
this.dropzone.on('sending', this.sending);
|
||||
this.dropzone.on('success', this.success);
|
||||
this.dropzone.on('error', this.error);
|
||||
}
|
||||
|
||||
sending(file, xhr) {
|
||||
xhr.timeout = 3600000;
|
||||
|
||||
$('.alert-danger').remove();
|
||||
}
|
||||
|
||||
success() {
|
||||
if (this.getUploadingFiles().length === 0 && this.getQueuedFiles().length === 0) {
|
||||
setTimeout(DataTable.reload, 1000, '#media-table .table');
|
||||
}
|
||||
}
|
||||
|
||||
error(file, response) {
|
||||
$('.dz-progress').css('z-index', 1);
|
||||
$(file.previewElement).find('.dz-error-message').text(response.message);
|
||||
}
|
||||
}
|
||||
13
Modules/Media/Resources/assets/admin/js/main.js
Normal file
13
Modules/Media/Resources/assets/admin/js/main.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import ImagePicker from './ImagePicker';
|
||||
import MediaPicker from './MediaPicker';
|
||||
import Uploader from './Uploader';
|
||||
|
||||
window.MediaPicker = MediaPicker;
|
||||
|
||||
if ($('.image-picker').length !== 0) {
|
||||
new ImagePicker();
|
||||
}
|
||||
|
||||
if ($('.dropzone').length !== 0) {
|
||||
new Uploader();
|
||||
}
|
||||
161
Modules/Media/Resources/assets/admin/sass/main.scss
Normal file
161
Modules/Media/Resources/assets/admin/sass/main.scss
Normal file
@@ -0,0 +1,161 @@
|
||||
@import '~dropzone/dist/dropzone';
|
||||
|
||||
.dropzone {
|
||||
border: 1px dashed #d2d6de;
|
||||
min-height: 232px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.dz-clickable {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.dz-message {
|
||||
color: #646C7F;
|
||||
font-size: 20px;
|
||||
margin-top: 82px;
|
||||
}
|
||||
|
||||
.dz-preview .dz-progress .dz-upload {
|
||||
background: #0068e1;
|
||||
}
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.main-file {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-thumbnail {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
|
||||
> .thumbnail-name {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.media-picker-divider {
|
||||
border-bottom: 1px solid #e9e9e9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.single-image-wrapper {
|
||||
padding-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.single-image {
|
||||
padding: 10px;
|
||||
background: #f1f1f1;
|
||||
margin-top: 15px;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
vertical-align: bottom;
|
||||
|
||||
> .image-holder {
|
||||
cursor: default;
|
||||
margin: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.multiple-images-wrapper {
|
||||
margin-top: 15px;
|
||||
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.multiple-images {
|
||||
overflow: hidden;
|
||||
padding: 10px 10px 0 10px;
|
||||
background: #f1f1f1;
|
||||
margin-top: 15px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.image-holder {
|
||||
position: relative;
|
||||
float: left;
|
||||
height: 125px;
|
||||
width: 125px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
margin: 0 10px 10px 0;
|
||||
border: 1px solid #d2d6de;
|
||||
border-radius: 3px;
|
||||
cursor: move;
|
||||
z-index: 0;
|
||||
|
||||
> i {
|
||||
position: absolute;
|
||||
font-size: 60px;
|
||||
color: #d9d9d9;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
> img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
> .remove-image {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: 4px;
|
||||
color: #ffffff;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
transition: 200ms;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
font-size: 18px;
|
||||
font-family: 'FontAwesome';
|
||||
-webkit-text-stroke: 1px #737881;
|
||||
-moz-text-stroke: 1px #737881;
|
||||
-ms-text-stroke: 1px #737881;
|
||||
-o-text-stroke: 1px #737881;
|
||||
z-index: 2;
|
||||
|
||||
&:focus {
|
||||
border-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "\f00d";
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .remove-image {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.image-picker > i {
|
||||
color: #737881;
|
||||
}
|
||||
78
Modules/Media/Resources/assets/admin/sass/media-picker.scss
Normal file
78
Modules/Media/Resources/assets/admin/sass/media-picker.scss
Normal file
@@ -0,0 +1,78 @@
|
||||
.file-manager-iframe {
|
||||
width: 100%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.file-manager {
|
||||
background: #f9f9f9;
|
||||
margin-top: 20px;
|
||||
overflow-x: auto;
|
||||
|
||||
#notification-toast {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.dataTable {
|
||||
.btn {
|
||||
padding: 10px 16px 8px;
|
||||
|
||||
> i {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-picker-modal {
|
||||
padding-right: 0 !important;
|
||||
z-index: 1050;
|
||||
|
||||
> i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: auto;
|
||||
margin: 25px auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: auto;
|
||||
float: none;
|
||||
border: 1px solid #d2d6de;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 6px 15px;
|
||||
background: #f1f1f1;
|
||||
|
||||
> .close {
|
||||
margin-top: 5px;
|
||||
-webkit-text-stroke: 0;
|
||||
transition: 200ms;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
background: #f9f9f9;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.modal.fade .modal-dialog {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
transition: 200ms ease-in-out;
|
||||
|
||||
&.in .modal-dialog {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.media-picker-modal .modal-dialog {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
19
Modules/Media/Resources/lang/en/media.php
Normal file
19
Modules/Media/Resources/lang/en/media.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'media' => 'Media',
|
||||
'drop_files_here' => 'Drop files here or click to upload',
|
||||
'upload_new_file' => 'Upload New File',
|
||||
'browse' => 'Browse',
|
||||
'thumbnails' => 'Thumbnails',
|
||||
'table' => [
|
||||
'thumbnail' => 'Thumbnail',
|
||||
'filename' => 'Filename',
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
],
|
||||
'file_manager' => [
|
||||
'title' => 'File Manager',
|
||||
'select_this_file' => 'Select this file',
|
||||
],
|
||||
];
|
||||
5
Modules/Media/Resources/lang/en/messages.php
Normal file
5
Modules/Media/Resources/lang/en/messages.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'image_has_been_added' => 'Image has been added.',
|
||||
];
|
||||
7
Modules/Media/Resources/lang/en/permissions.php
Normal file
7
Modules/Media/Resources/lang/en/permissions.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'index' => 'Index Media',
|
||||
'create' => 'Create Media',
|
||||
'destroy' => 'Delete Media',
|
||||
];
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ locale() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title>{{ trans('media::media.file_manager.title') }}</title>
|
||||
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:600|Roboto" rel="stylesheet">
|
||||
|
||||
@foreach ($assets->allCss() as $css)
|
||||
<link media="all" type="text/css" rel="stylesheet" href="{{ v($css) }}">
|
||||
@endforeach
|
||||
|
||||
@include('admin::partials.globals')
|
||||
</head>
|
||||
|
||||
<body class="file-manager">
|
||||
<div class="container">
|
||||
@include('media::admin.media.partials.uploader')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box box-primary">
|
||||
@include('media::admin.media.partials.table')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="notification-toast"></div>
|
||||
|
||||
@include('admin::partials.confirmation_modal')
|
||||
|
||||
@foreach ($assets->allJs() as $js)
|
||||
<script src="{{ v($js) }}"></script>
|
||||
@endforeach
|
||||
|
||||
<script>
|
||||
DataTable.setRoutes('.file-manager .table', {
|
||||
index: {
|
||||
name: 'admin.media.index',
|
||||
params: { type: '{{ $type }}' }
|
||||
},
|
||||
destroy: 'admin.media.destroy',
|
||||
});
|
||||
|
||||
new DataTable('.file-manager .table', {
|
||||
columns: [
|
||||
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
|
||||
{ data: 'id', width: '5%' },
|
||||
{ data: 'thumbnail', orderable: false, searchable: false, width: '10%' },
|
||||
{ data: 'filename', name: 'filename' },
|
||||
{ data: 'created', name: 'created_at' },
|
||||
{ data: 'action', orderable: false, searchable: false },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="multiple-images-wrapper">
|
||||
<h4>{{ $title }}</h4>
|
||||
|
||||
<button type="button" class="image-picker btn btn-default" data-input-name="{{ $inputName }}" data-multiple>
|
||||
<i class="fa fa-folder-open m-r-5"></i>{{ trans('media::media.browse') }}
|
||||
</button>
|
||||
|
||||
<div class="multiple-images">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="image-list image-holder-wrapper clearfix">
|
||||
@if ($files->isEmpty())
|
||||
<div class="image-holder placeholder cursor-auto">
|
||||
<i class="fa fa-picture-o"></i>
|
||||
</div>
|
||||
@else
|
||||
@foreach ($files as $file)
|
||||
<div class="image-holder">
|
||||
<img src="{{ $file->path }}">
|
||||
<button type="button" class="btn remove-image" data-input-name="{{ $inputName }}"></button>
|
||||
<input type="hidden" name="{{ $inputName }}" value="{{ $file->id }}">
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
@hasAccess('admin.media.index')
|
||||
<div class="single-image-wrapper">
|
||||
<h4>{{ $title }}</h4>
|
||||
|
||||
<button type="button" class="image-picker btn btn-default" data-input-name="{{ $inputName }}">
|
||||
<i class="fa fa-folder-open m-r-5"></i>{{ trans('media::media.browse') }}
|
||||
</button>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="single-image image-holder-wrapper clearfix">
|
||||
@if (! $file->exists)
|
||||
<div class="image-holder placeholder">
|
||||
<i class="fa fa-picture-o"></i>
|
||||
</div>
|
||||
@else
|
||||
<div class="image-holder">
|
||||
<img src="{{ $file->path }}">
|
||||
<button type="button" class="btn remove-image" data-input-name="{{ $inputName }}"></button>
|
||||
<input type="hidden" name="{{ $inputName }}" value="{{ $file->id }}">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endHasAccess
|
||||
51
Modules/Media/Resources/views/admin/media/index.blade.php
Normal file
51
Modules/Media/Resources/views/admin/media/index.blade.php
Normal file
@@ -0,0 +1,51 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('media::media.media'))
|
||||
|
||||
<li class="active">{{ trans('media::media.media') }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
@include('media::admin.media.partials.uploader')
|
||||
|
||||
<div class="box box-primary">
|
||||
<div class="box-header"></div>
|
||||
|
||||
@include('media::admin.media.partials.table')
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('shortcuts')
|
||||
<dl class="dl-horizontal">
|
||||
<dt><code>u</code></dt>
|
||||
<dd>{{ trans('media::media.upload_new_file') }}</dd>
|
||||
</dl>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
Mousetrap.bind('u', function() {
|
||||
$('.dropzone').trigger('click');
|
||||
});
|
||||
|
||||
Mousetrap.bind('del', function () {
|
||||
$('.btn-delete').trigger('click');
|
||||
});
|
||||
|
||||
DataTable.setRoutes('#media-table .table', {
|
||||
index: 'admin.media.index',
|
||||
destroy: 'admin.media.destroy',
|
||||
});
|
||||
|
||||
new DataTable('#media-table .table', {
|
||||
columns: [
|
||||
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
|
||||
{ data: 'id', width: '5%' },
|
||||
{ data: 'thumbnail', orderable: false, searchable: false, width: '10%' },
|
||||
{ data: 'filename' },
|
||||
{ data: 'created', name: 'created_at' },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="box-body index-table" id="media-table">
|
||||
@component('admin::components.table')
|
||||
@slot('thead')
|
||||
<tr>
|
||||
@include('admin::partials.table.select_all')
|
||||
|
||||
<th data-sort>{{ trans('admin::admin.table.id') }}</th>
|
||||
<th>{{ trans('media::media.table.thumbnail') }}</th>
|
||||
<th>{{ trans('media::media.table.filename') }}</th>
|
||||
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
|
||||
|
||||
@unless (request()->routeIs('admin.media.index'))
|
||||
<th class="min-tablet"></th>
|
||||
@endif
|
||||
</tr>
|
||||
@endslot
|
||||
@endcomponent
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<button type="button" class="btn btn-default select-media"
|
||||
data-id="{{ $file->id }}"
|
||||
data-path="{{ $file->path }}"
|
||||
data-filename="{{ $file->filename }}"
|
||||
data-type="{{ strtok($file->mime, '/') }}"
|
||||
data-icon="{{ $file->icon() }}"
|
||||
data-toggle="tooltip"
|
||||
title="{{ trans('media::media.file_manager.select_this_file') }}"
|
||||
>
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
</button>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="thumbnail-holder">
|
||||
@if ($file->isImage())
|
||||
<img src="{{ $file->path }}" alt="thumbnail">
|
||||
@else
|
||||
<i class="file-icon fa {{ $file->icon() }}"></i>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
@push('globals')
|
||||
<script>
|
||||
FleetCart.maxFileSize = {{ (int) ini_get('upload_max_filesize') }}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form method="POST" class="dropzone">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="dz-message needsclick">
|
||||
{{ trans('media::media.drop_files_here') }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
27
Modules/Media/Routes/admin.php
Normal file
27
Modules/Media/Routes/admin.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('media', [
|
||||
'as' => 'admin.media.index',
|
||||
'uses' => 'MediaController@index',
|
||||
'middleware' => 'can:admin.media.index',
|
||||
]);
|
||||
|
||||
Route::post('media', [
|
||||
'as' => 'admin.media.store',
|
||||
'uses' => 'MediaController@store',
|
||||
'middleware' => 'can:admin.media.create',
|
||||
]);
|
||||
|
||||
Route::delete('media/{ids?}', [
|
||||
'as' => 'admin.media.destroy',
|
||||
'uses' => 'MediaController@destroy',
|
||||
'middleware' => 'can:admin.media.destroy',
|
||||
]);
|
||||
|
||||
Route::get('file-manager', [
|
||||
'uses' => 'FileManagerController@index',
|
||||
'as' => 'admin.file_manager.index',
|
||||
'middleware' => 'can:admin.media.index',
|
||||
]);
|
||||
25
Modules/Media/Sidebar/SidebarExtender.php
Normal file
25
Modules/Media/Sidebar/SidebarExtender.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Media\Sidebar;
|
||||
|
||||
use Maatwebsite\Sidebar\Item;
|
||||
use Maatwebsite\Sidebar\Menu;
|
||||
use Maatwebsite\Sidebar\Group;
|
||||
use Modules\Admin\Sidebar\BaseSidebarExtender;
|
||||
|
||||
class SidebarExtender extends BaseSidebarExtender
|
||||
{
|
||||
public function extend(Menu $menu)
|
||||
{
|
||||
$menu->group(trans('admin::sidebar.content'), function (Group $group) {
|
||||
$group->item(trans('media::media.media'), function (Item $item) {
|
||||
$item->weight(30);
|
||||
$item->icon('fa fa-camera');
|
||||
$item->route('admin.media.index');
|
||||
$item->authorize(
|
||||
$this->auth->hasAccess('admin.media.index')
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
27
Modules/Media/composer.json
Normal file
27
Modules/Media/composer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fleetcart/media",
|
||||
"description": "The FleetCart Media Module.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Envay Soft",
|
||||
"email": "envaysoft@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Media\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
9
Modules/Media/module.json
Normal file
9
Modules/Media/module.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Media",
|
||||
"alias": "media",
|
||||
"description": "The FleetCart Media Module.",
|
||||
"priority": 80,
|
||||
"providers": [
|
||||
"Modules\\Media\\Providers\\MediaServiceProvider"
|
||||
]
|
||||
}
|
||||
7
Modules/Media/package.json
Normal file
7
Modules/Media/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "media-module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"dropzone": "^5.4.0"
|
||||
}
|
||||
}
|
||||
8
Modules/Media/webpack.mix.js
Normal file
8
Modules/Media/webpack.mix.js
Normal file
@@ -0,0 +1,8 @@
|
||||
let mix = require('laravel-mix');
|
||||
let execSync = require('child_process').execSync;
|
||||
|
||||
mix.js('Modules/Media/Resources/assets/admin/js/main.js', 'Modules/Media/Assets/admin/js/media.js')
|
||||
.sass('Modules/Media/Resources/assets/admin/sass/main.scss', 'Modules/Media/Assets/admin/css/media.css')
|
||||
.then(() => {
|
||||
execSync('npm run rtlcss Modules/Media/Assets/admin/css/media.css Modules/Media/Assets/admin/css/media.rtl.css');
|
||||
});
|
||||
7
Modules/Media/yarn.lock
Normal file
7
Modules/Media/yarn.lock
Normal file
@@ -0,0 +1,7 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
dropzone@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.4.0.tgz#3290c07f59b189eb5a11e99a58c9b2adae5acfec"
|
||||
Reference in New Issue
Block a user