├── resources ├── images │ ├── file.png │ ├── audio.png │ ├── image.png │ ├── upload.png │ └── video.png ├── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Thin.ttf │ ├── Inter-Black.ttf │ ├── Inter-Light.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ ├── Inter-ExtraBold.ttf │ ├── Inter-SemiBold.ttf │ ├── Inter-ExtraLight.ttf │ ├── README.txt │ └── OFL.txt ├── icons │ ├── exit.blade.php │ ├── close.blade.php │ ├── arrow.blade.php │ └── logo.blade.php ├── views │ ├── tailwind │ │ ├── includes │ │ │ ├── editor.blade.php │ │ │ ├── header.blade.php │ │ │ ├── previews.blade.php │ │ │ ├── strip.blade.php │ │ │ ├── alert.blade.php │ │ │ ├── stats.blade.php │ │ │ ├── footer.blade.php │ │ │ ├── sidebar.blade.php │ │ │ ├── meta.blade.php │ │ │ ├── attachments.blade.php │ │ │ ├── pagination.blade.php │ │ │ ├── uploads.blade.php │ │ │ ├── toolbar.blade.php │ │ │ └── form.blade.php │ │ └── media-browser.blade.php │ └── assets │ │ ├── scripts.blade.php │ │ └── styles.blade.php ├── database │ └── migrations │ │ └── 2023_02_19_002734_create_attachments_table.php └── config │ └── config.php ├── pint.json ├── src ├── Exceptions │ ├── MediaSourceException.php │ ├── MediaBrowserException.php │ └── MediaExtensionException.php ├── Assets │ ├── Styles.php │ └── Scripts.php ├── Eloquent │ ├── Eloquent.php │ └── EloquentManager.php ├── GraphicDraw │ ├── GraphicDraw.php │ ├── GraphicDrawManager.php │ └── GraphicDrawBase.php ├── Enums │ └── BrowserEvents.php ├── Traits │ ├── WithReporting.php │ ├── WithExtension.php │ ├── WithFileSize.php │ ├── WithCache.php │ ├── WithFonts.php │ ├── WithColumnWidths.php │ ├── WithStorage.php │ ├── WithMimeTypes.php │ ├── ServerLimits.php │ └── WithGraphicDraw.php ├── Concerns │ ├── AlertState.php │ ├── ModalState.php │ ├── PanelState.php │ ├── ShowState.php │ └── AttachmentState.php ├── Models │ └── Attachment.php ├── Console │ └── Commands │ │ ├── InstallCommand.php │ │ └── UpdateCommand.php ├── Providers │ └── MediableServiceProvider.php └── Components │ └── MediaBrowser.php ├── LICENSE ├── composer.json ├── CHANGELOG.md ├── CONTRIBUTING.md └── README.md /resources/images/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/images/file.png -------------------------------------------------------------------------------- /resources/images/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/images/audio.png -------------------------------------------------------------------------------- /resources/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/images/image.png -------------------------------------------------------------------------------- /resources/images/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/images/upload.png -------------------------------------------------------------------------------- /resources/images/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/images/video.png -------------------------------------------------------------------------------- /resources/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Thin.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Black.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Light.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-ExtraBold.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /resources/fonts/Inter-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomshaw/mediable/HEAD/resources/fonts/Inter-ExtraLight.ttf -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "single_import_per_statement": false, 5 | "group_import": true 6 | } 7 | } -------------------------------------------------------------------------------- /src/Exceptions/MediaSourceException.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Assets/Styles.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/editor.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if ($this->mimeTypeImage($this->attachment->file_type)) 3 |
4 | 5 |
6 | @endif 7 |
-------------------------------------------------------------------------------- /src/Traits/WithReporting.php: -------------------------------------------------------------------------------- 1 | addHours(24)); 12 | } 13 | 14 | public function hasStoreAttachmentId(): bool 15 | { 16 | return Cache::has('attachment_id'); 17 | } 18 | 19 | public function getStoreAttachmentId(): ?int 20 | { 21 | return Cache::get('attachment_id'); 22 | } 23 | 24 | public function deleteStoreAttachmentId(): void 25 | { 26 | Cache::forget('attachment_id'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Concerns/AlertState.php: -------------------------------------------------------------------------------- 1 | $this->show, 19 | 'type' => $this->type, 20 | 'message' => $this->message, 21 | ]; 22 | } 23 | 24 | public static function fromLivewire($value) 25 | { 26 | return new self( 27 | show: $value['show'], 28 | type: $value['type'], 29 | message: $value['message'] 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Concerns/ModalState.php: -------------------------------------------------------------------------------- 1 | $this->show, 18 | 'elementId' => $this->elementId, 19 | ]; 20 | } 21 | 22 | public static function fromLivewire($value) 23 | { 24 | return new self( 25 | show: $value['show'], 26 | elementId: $value['elementId'], 27 | ); 28 | } 29 | 30 | public function hasElementId(): bool 31 | { 32 | return ! empty($this->elementId); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/header.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 7 |
8 | 9 | @if($show->isShowSearch()) 10 |
11 |
12 | 13 |
14 |
15 | @endif 16 | 17 |
18 | 21 |
22 | 23 |
-------------------------------------------------------------------------------- /resources/views/tailwind/includes/previews.blade.php: -------------------------------------------------------------------------------- 1 |
mimeTypeImage($this->attachment->file_type)) ? 'h-auto' : 'h-full'])> 2 | @if ($this->mimeTypeImage($this->attachment->file_type)) 3 |
4 | 5 |
6 | @elseif ($this->mimeTypeVideo($this->attachment->file_type)) 7 |
8 | 9 |
10 | @elseif ($this->mimeTypeAudio($this->attachment->file_type)) 11 |
12 | 15 |
16 | @endif 17 |
-------------------------------------------------------------------------------- /src/Models/Attachment.php: -------------------------------------------------------------------------------- 1 | count()) 2 | 19 | @endif -------------------------------------------------------------------------------- /src/Traits/WithFonts.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('file_name')->nullable(); 17 | $table->string('file_original_name')->nullable(); 18 | $table->string('file_type')->nullable(); 19 | $table->integer('file_size')->default('0')->nullable(); 20 | $table->string('file_dir')->nullable(); 21 | $table->string('file_url')->nullable(); 22 | $table->string('title')->nullable(); 23 | $table->string('caption')->nullable(); 24 | $table->text('description')->nullable(); 25 | $table->integer('sort_order')->default('0')->nullable(); 26 | $table->string('styles')->nullable(); 27 | $table->boolean('hidden')->default(false); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | */ 35 | public function down(): void 36 | { 37 | Schema::dropIfExists('attachments'); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/Traits/WithColumnWidths.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |
-------------------------------------------------------------------------------- /resources/views/assets/scripts.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Concerns/PanelState.php: -------------------------------------------------------------------------------- 1 | thumbMode; 20 | } 21 | 22 | public function isPreviewMode(): bool 23 | { 24 | return $this->previewMode; 25 | } 26 | 27 | public function isUploadMode(): bool 28 | { 29 | return $this->uploadMode; 30 | } 31 | 32 | public function isEditorMode(): bool 33 | { 34 | return $this->editorMode; 35 | } 36 | 37 | public function isFormMode(): bool 38 | { 39 | return $this->formMode; 40 | } 41 | 42 | public function toLivewire() 43 | { 44 | return [ 45 | 'thumbMode' => $this->thumbMode, 46 | 'previewMode' => $this->previewMode, 47 | 'uploadMode' => $this->uploadMode, 48 | 'editorMode' => $this->editorMode, 49 | 'formMode' => $this->formMode, 50 | ]; 51 | } 52 | 53 | public static function fromLivewire($value) 54 | { 55 | return new self( 56 | thumbMode: $value['thumbMode'], 57 | previewMode: $value['previewMode'], 58 | uploadMode: $value['uploadMode'], 59 | editorMode: $value['editorMode'], 60 | formMode: $value['formMode'] 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/alert.blade.php: -------------------------------------------------------------------------------- 1 |
8 |
9 | {{$alert->message}} 10 |
11 |
12 | 13 |
14 |
15 | @script 16 | 41 | @endscript -------------------------------------------------------------------------------- /resources/icons/logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/stats.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 | @if ($this->mimeTypeTotals->total) 15 |
16 | FILES 17 | {{ $this->mimeTypeTotals->total }} – {{ $this->formatBytes($this->mimeTypeTotals->total_size) }} 18 |
19 | @endif 20 | 21 | @foreach($this->mimeTypeStats as $item) 22 |
23 | {{ strtoupper(collect(explode('/', $item->file_type))->last()) }} 24 | {{ $item->total }} – {{ $this->formatBytes($item->total_size) }} 25 |
26 | @endforeach 27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 |
-------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomshaw/mediable", 3 | "description": "A comprehensive Laravel Livewire Media Manager.", 4 | "keywords": [ 5 | "laravel", 6 | "livewire" 7 | ], 8 | "homepage": "https://github.com/tomshaw/mediable", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Tom Shaw", 13 | "email": "tomshaw@tomshaw.us", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.2|^8.3|^8.4", 19 | "laravel/framework": "^11.0", 20 | "illuminate/console": "^11.0", 21 | "illuminate/contracts": "^11.0", 22 | "illuminate/support": "^11.0", 23 | "livewire/livewire": "^3.4" 24 | }, 25 | "require-dev": { 26 | "larastan/larastan": "^2.9", 27 | "laravel/pint": "^1.14", 28 | "nunomaduro/collision": "^8.1", 29 | "orchestra/testbench": "9.x-dev", 30 | "pestphp/pest": "3.x-dev" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "TomShaw\\Mediable\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "TomShaw\\Mediable\\Tests\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "analyse": "vendor/bin/phpstan analyse --ansi --memory-limit=-1", 44 | "baseline": "vendor/bin/phpstan analyse --ansi --memory-limit=-1 --generate-baseline --allow-empty-baseline", 45 | "format": "vendor/bin/pint --config pint.json", 46 | "test": "vendor/bin/pest", 47 | "test-coverage": "vendor/bin/pest --coverage" 48 | }, 49 | "config": { 50 | "sort-packages": true, 51 | "allow-plugins": { 52 | "pestphp/pest-plugin": true 53 | } 54 | }, 55 | "extra": { 56 | "laravel": { 57 | "providers": [ 58 | "TomShaw\\Mediable\\Providers\\MediableServiceProvider" 59 | ] 60 | } 61 | }, 62 | "minimum-stability": "dev", 63 | "prefer-stable": true 64 | } 65 | -------------------------------------------------------------------------------- /src/Console/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | comment('Publishing Mediable Config...'); 31 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.config']); 32 | 33 | $this->comment('Publishing Mediable Views...'); 34 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.views']); 35 | 36 | $this->comment('Publishing Mediable Images...'); 37 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.images']); 38 | 39 | $this->comment('Publishing Mediable Fonts...'); 40 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.fonts']); 41 | 42 | $this->comment('Building Mediable Assets...'); 43 | $this->buildAssets(); 44 | 45 | $this->info('Mediable installed successfully!'); 46 | } 47 | 48 | private function buildAssets() 49 | { 50 | $process = new Process(['npm', 'run', 'build']); 51 | 52 | $process->setWorkingDirectory(base_path()) 53 | ->setTimeout(null) 54 | ->run(function ($type, $buffer) { 55 | if ($type === Process::ERR) { 56 | $this->error($buffer); 57 | } else { 58 | $this->line($buffer); 59 | } 60 | }); 61 | 62 | if (! $process->isSuccessful()) { 63 | throw new RuntimeException($process->getErrorOutput()); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.7.2](https://github.com/tomshaw/mediable/compare/v0.7.1...v0.7.2) (2025-03-08) 4 | 5 | 6 | ### Miscellaneous Chores 7 | 8 | * minor adjustments, bump version to 0.7.2 ([5a39261](https://github.com/tomshaw/mediable/commit/5a39261442615c1a7213205b7b0a80a77e23588e)) 9 | 10 | ## [0.7.1](https://github.com/tomshaw/mediable/compare/v0.7.0...v0.7.1) (2025-03-06) 11 | 12 | 13 | ### Miscellaneous Chores 14 | 15 | * **release:** bump version to 0.7.1 ([c362ef7](https://github.com/tomshaw/mediable/commit/c362ef7cb1f93eb08af4d59f2bdff8069ed407ec)) 16 | 17 | ## [0.7.0](https://github.com/tomshaw/mediable/compare/v0.6.1...v0.7.0) (2025-03-05) 18 | 19 | 20 | ### Features 21 | 22 | * added option to set file system folder ([8079bdb](https://github.com/tomshaw/mediable/commit/8079bdb40e3c3894166f76f316d78e94de984fad)) 23 | 24 | ## [0.6.1](https://github.com/tomshaw/mediable/compare/v0.6.0...v0.6.1) (2025-03-05) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * attempt to quickly fix latest updates ([07f8e5f](https://github.com/tomshaw/mediable/commit/07f8e5ff7e81a6cd8b9073571294449ad58932e5)) 30 | 31 | ## [0.6.0](https://github.com/tomshaw/mediable/compare/v0.5.2...v0.6.0) (2025-03-04) 32 | 33 | 34 | ### Features 35 | 36 | * save files as publicly stored with SEO-friendly file names ([9afe078](https://github.com/tomshaw/mediable/commit/9afe078380b8031039a32172cecb861af9f15ee7)) 37 | 38 | ## [0.5.2](https://github.com/tomshaw/mediable/compare/v0.5.1...v0.5.2) (2025-02-03) 39 | 40 | 41 | ### Miscellaneous Chores 42 | 43 | * **deps:** add support for PHP 8.3 and 8.4 in composer.json ([99a15f5](https://github.com/tomshaw/mediable/commit/99a15f5faf01682512283ba7a8e87e1bf68d8d3a)) 44 | 45 | ## [0.5.1](https://github.com/tomshaw/mediable/compare/v0.5.0...v0.5.1) (2024-04-19) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * Wrong link in Github issue template. ([48acdec](https://github.com/tomshaw/mediable/commit/48acdec841fd17bfb57c825ae8ee1cd8280fb785)) 51 | 52 | ## [0.5.0](https://github.com/tomshaw/mediable/compare/v0.4.0...v0.5.0) (2024-04-19) 53 | 54 | 55 | ### Features 56 | 57 | * added release please github workflow. ([b58fa2c](https://github.com/tomshaw/mediable/commit/b58fa2c866eb4d25085488d5c5e67692a36e7520)) 58 | -------------------------------------------------------------------------------- /src/Console/Commands/UpdateCommand.php: -------------------------------------------------------------------------------- 1 | info('This will overwrite Mediable (config, views, images, fonts).'); 31 | 32 | if ($this->confirm('Do you wish to continue?', true)) { 33 | $this->comment('Updating Mediable Config...'); 34 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.config', '--force' => true]); 35 | 36 | $this->comment('Updating Mediable Assets...'); 37 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.views', '--force' => true]); 38 | 39 | $this->comment('Updating Mediable Images...'); 40 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.images', '--force' => true]); 41 | 42 | $this->comment('Updating Mediable Fonts...'); 43 | $this->callSilent('vendor:publish', ['--tag' => 'mediable.fonts', '--force' => true]); 44 | 45 | $this->comment('Building Mediable Assets...'); 46 | $this->buildAssets(); 47 | } 48 | 49 | $this->info('Mediable updated successfully!'); 50 | } 51 | 52 | private function buildAssets() 53 | { 54 | $process = new Process(['npm', 'run', 'build']); 55 | 56 | $process->setWorkingDirectory(base_path()) 57 | ->setTimeout(null) 58 | ->run(function ($type, $buffer) { 59 | if ($type === Process::ERR) { 60 | $this->error($buffer); 61 | } else { 62 | $this->line($buffer); 63 | } 64 | }); 65 | 66 | if (! $process->isSuccessful()) { 67 | throw new RuntimeException($process->getErrorOutput()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/GraphicDraw/GraphicDrawManager.php: -------------------------------------------------------------------------------- 1 | create($filename); 10 | 11 | if ($image === false) { 12 | return false; 13 | } 14 | 15 | $this->flip($image, $mode); 16 | 17 | return $this->save($filename, $image); 18 | } 19 | 20 | public function filterAndSave(string $filename, int $filter, array $args = []): bool 21 | { 22 | $image = $this->create($filename); 23 | 24 | if ($image === false) { 25 | return false; 26 | } 27 | 28 | $this->filter($image, $filter, $args); 29 | 30 | return $this->save($filename, $image); 31 | } 32 | 33 | public function scaleAndSave(string $filename, int $new_width, int $new_height = -1, int $mode = IMG_BILINEAR_FIXED): bool 34 | { 35 | $image = $this->create($filename); 36 | 37 | if ($image === false) { 38 | return false; 39 | } 40 | 41 | $result = $this->scale($image, $new_width, $new_height, $mode); 42 | 43 | return $this->save($filename, $result); 44 | } 45 | 46 | public function rotateAndSave(string $filename, float $angle, int $bgd_color, bool $ignore_transparent = false): bool 47 | { 48 | $image = $this->create($filename); 49 | 50 | if ($image === false) { 51 | return false; 52 | } 53 | 54 | $result = $this->rotate($image, $angle, $bgd_color, $ignore_transparent); 55 | 56 | return $this->save($filename, $result); 57 | } 58 | 59 | public function cropAndSave(string $filename, array $rect): bool 60 | { 61 | $image = $this->create($filename); 62 | 63 | if ($image === false) { 64 | return false; 65 | } 66 | 67 | $result = $this->crop($image, $rect); 68 | 69 | return $this->save($filename, $result); 70 | } 71 | 72 | public function textAndSave(string $filename, float $size, float $angle, int $x, int $y, int $color, string $fontfile, string $text): bool 73 | { 74 | $image = $this->create($filename); 75 | 76 | if ($image === false) { 77 | return false; 78 | } 79 | 80 | $this->text($image, $size, $angle, $x, $y, $color, $fontfile, $text); 81 | 82 | return $this->save($filename, $image); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /resources/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Inter Variable Font 2 | =================== 3 | 4 | This download contains Inter as both a variable font and static fonts. 5 | 6 | Inter is a variable font with these axes: 7 | slnt 8 | wght 9 | 10 | This means all the styles are contained in a single file: 11 | Inter/Inter-VariableFont_slnt,wght.ttf 12 | 13 | If your app fully supports variable fonts, you can now pick intermediate styles 14 | that aren’t available as static fonts. Not all apps support variable fonts, and 15 | in those cases you can use the static font files for Inter: 16 | Inter/static/Inter-Thin.ttf 17 | Inter/static/Inter-ExtraLight.ttf 18 | Inter/static/Inter-Light.ttf 19 | Inter/static/Inter-Regular.ttf 20 | Inter/static/Inter-Medium.ttf 21 | Inter/static/Inter-SemiBold.ttf 22 | Inter/static/Inter-Bold.ttf 23 | Inter/static/Inter-ExtraBold.ttf 24 | Inter/static/Inter-Black.ttf 25 | 26 | Get started 27 | ----------- 28 | 29 | 1. Install the font files you want to use 30 | 31 | 2. Use your app's font picker to view the font family and all the 32 | available styles 33 | 34 | Learn more about variable fonts 35 | ------------------------------- 36 | 37 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 38 | https://variablefonts.typenetwork.com 39 | https://medium.com/variable-fonts 40 | 41 | In desktop apps 42 | 43 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 44 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 45 | 46 | Online 47 | 48 | https://developers.google.com/fonts/docs/getting_started 49 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 50 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 51 | 52 | Installing fonts 53 | 54 | MacOS: https://support.apple.com/en-us/HT201749 55 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 56 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 57 | 58 | Android Apps 59 | 60 | https://developers.google.com/fonts/docs/android 61 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 62 | 63 | License 64 | ------- 65 | Please read the full license text (OFL.txt) to understand the permissions, 66 | restrictions and requirements for usage, redistribution, and modification. 67 | 68 | You can use them in your products & projects – print or digital, 69 | commercial or otherwise. 70 | 71 | This isn't legal advice, please consider consulting a lawyer and see the full 72 | license for all details. 73 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/footer.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | @if (count($selected)) 4 | 8 | 12 | @endif 13 | 30 |
31 |
32 | @if (count($selected)) 33 | 37 | @endif 38 |
39 |
-------------------------------------------------------------------------------- /resources/config/config.php: -------------------------------------------------------------------------------- 1 | 'tailwind', 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Validation Allowable File Types/Max File Size 18 | |-------------------------------------------------------------------------- 19 | | 20 | | 5120 = 5MB Max 21 | | 10240 = 10MB Max 22 | | 51200 = 50MB Max 23 | | 102400 = 100MB Max 24 | | 204800 = 200MB Max 25 | | 26 | */ 27 | 'validation' => [ 28 | 'files.*' => 'required|mimes:jpeg,png,jpg,gif,mp3,mp4,m4a,ogg,wav,webm,avi,mov,wmv,txt,pdf,doc,docx,xls,xlsx,ppt,pptx,zip,rar|max:10240', 29 | ], 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Storage Disk 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The disk defined in the filesystems.php config file to use for storing 37 | | uploaded files. 38 | */ 39 | 'disk' => env('MEDIABLE_DISK_DRIVER', 'public'), 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Folder 44 | |-------------------------------------------------------------------------- 45 | | 46 | | The folder on the system disk to store uploaded files. 47 | */ 48 | 'folder' => env('MEDIABLE_DISK_FOLDER', 'uploads'), 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Image Conversion Settings 53 | |-------------------------------------------------------------------------- 54 | | 55 | | These settings control the creation of WebP and AVIF versions of image uploads. 56 | | 57 | | 'create_webp' and 'create_avif' determine whether to create WebP and AVIF versions, respectively. 58 | | These can be set to true or false. By default, both are set to true. 59 | | 60 | | 'webp_quality' and 'avif_quality' control the quality of the WebP and AVIF versions, respectively. 61 | | These can be set to any integer between 0 and 100. By default, both are set to 80. 62 | | 63 | */ 64 | 'create_webp' => env('MEDIABLE_CREATE_WEBP', true), 65 | 'create_avif' => env('MEDIABLE_CREATE_AVIF', true), 66 | 'webp_quality' => env('MEDIABLE_WEBP_QUALITY', 80), 67 | 'avif_quality' => env('MEDIABLE_AVIF_QUALITY', 80), 68 | ]; 69 | -------------------------------------------------------------------------------- /src/Traits/WithStorage.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'image/jpeg', 10 | 'image/png', 11 | 'image/gif', 12 | 'image/webp', 13 | 'image/avif', 14 | 'image/bmp', 15 | 'image/tiff', 16 | ], 17 | 'video' => [ 18 | 'video/3gpp', 19 | 'video/mpeg', 20 | 'video/mp4', 21 | 'video/ogg', 22 | 'video/quicktime', 23 | 'video/webm', 24 | 'video/x-flv', 25 | 'video/x-msvideo', 26 | ], 27 | 'audio' => [ 28 | 'audio/aac', 29 | 'audio/flac', 30 | 'audio/midi', 31 | 'audio/mpeg', 32 | 'audio/ogg', 33 | 'audio/ogg', 34 | 'audio/opus', 35 | 'audio/wav', 36 | 'audio/webm', 37 | 'audio/x-midi', 38 | 'audio/x-wav', 39 | ], 40 | 'document' => [ 41 | 'application/pdf', 42 | 'application/msword', 43 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 44 | 'application/vnd.ms-excel', 45 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 46 | 'application/vnd.ms-powerpoint', 47 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 48 | 'text/plain', 49 | 'application/rtf', 50 | 'application/vnd.oasis.opendocument.text', 51 | 'application/vnd.oasis.opendocument.spreadsheet', 52 | 'application/vnd.oasis.opendocument.presentation', 53 | 'application/vnd.visio', 54 | 'application/vnd.ms-outlook', 55 | 'application/xml', 56 | 'text/csv', 57 | ], 58 | 'archive' => [ 59 | 'application/zip', 60 | 'application/x-rar-compressed', 61 | 'application/x-7z-compressed', 62 | 'application/x-tar', 63 | 'application/gzip', 64 | 'application/x-bzip2', 65 | 'application/x-zip-compressed', 66 | 'application/x-lzma', 67 | 'application/x-lzx', 68 | 'application/x-gtar', 69 | 'application/x-gzip', 70 | 'application/x-lzh', 71 | 'application/x-lha', 72 | 'application/x-tar', 73 | 'application/x-compress', 74 | 'application/x-compressed', 75 | 'application/x-stuffit', 76 | 'application/x-stuffitx', 77 | 'application/x-gtar', 78 | 'application/x-gzip', 79 | 'application/vnd.android.package-archive', 80 | ], 81 | ]; 82 | 83 | public function mimeTypeImage(string $mimeType): bool 84 | { 85 | return in_array($mimeType, $this->strategies['image']); 86 | } 87 | 88 | public function mimeTypeAudio(string $mimeType): bool 89 | { 90 | return in_array($mimeType, $this->strategies['audio']); 91 | } 92 | 93 | public function mimeTypeVideo(string $mimeType): bool 94 | { 95 | return in_array($mimeType, $this->strategies['video']); 96 | } 97 | 98 | public function mimeTypeDocument(string $mimeType): bool 99 | { 100 | return in_array($mimeType, $this->strategies['document']); 101 | } 102 | 103 | public function mimeTypeArchive(string $mimeType): bool 104 | { 105 | return in_array($mimeType, $this->strategies['archive']); 106 | } 107 | 108 | public function formatMimeType(string $mimeType): string 109 | { 110 | $parts = explode('/', $mimeType); 111 | 112 | return strtoupper(end($parts)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/sidebar.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | 40 | 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
-------------------------------------------------------------------------------- /src/Traits/ServerLimits.php: -------------------------------------------------------------------------------- 1 | convertToBytes(ini_get('upload_max_filesize')); 23 | $maxPost = $this->convertToBytes(ini_get('post_max_size')); 24 | $memoryLimit = $this->convertToBytes(ini_get('memory_limit')); 25 | 26 | return min($maxUpload, $maxPost, $memoryLimit); 27 | } 28 | 29 | /** 30 | * Get the maximum number of files that can be uploaded simultaneously. 31 | * 32 | * This method retrieves the 'max_file_uploads' configuration from the php.ini file. 33 | * The 'max_file_uploads' configuration directive limits the number of files that can be 34 | * uploaded in one request. 35 | * 36 | * @return int The maximum number of files that can be uploaded simultaneously. 37 | */ 38 | public function getMaxFileUploads(): int 39 | { 40 | return (int) ini_get('max_file_uploads'); 41 | } 42 | 43 | /** 44 | * Get the maximum upload file size from the PHP configuration. 45 | * 46 | * This method retrieves the 'upload_max_filesize' configuration from the php.ini file, 47 | * converts it to an integer, and returns it. 48 | * 49 | * @return int The maximum upload file size in bytes. 50 | */ 51 | public function getMaxUploadFileSize(): int 52 | { 53 | return $this->convertToBytes(ini_get('upload_max_filesize')); 54 | } 55 | 56 | /** 57 | * Get the maximum size of POST data that PHP will accept. 58 | * 59 | * This method retrieves the 'post_max_size' configuration from the php.ini file, 60 | * converts it to an integer, and returns it. 61 | * 62 | * @return int The maximum size of POST data that PHP will accept, in bytes. 63 | */ 64 | public function getPostMaxSize(): int 65 | { 66 | return $this->convertToBytes(ini_get('post_max_size')); 67 | } 68 | 69 | /** 70 | * Get the maximum amount of memory that a script is allowed to consume. 71 | * 72 | * This method retrieves the 'memory_limit' configuration from the php.ini file, 73 | * converts it to an integer, and returns it. 74 | * 75 | * @return int The maximum amount of memory a script is allowed to consume, in bytes. 76 | */ 77 | public function getMemoryLimit(): int 78 | { 79 | return $this->convertToBytes(ini_get('memory_limit')); 80 | } 81 | 82 | /** 83 | * Convert a shorthand byte value from a PHP configuration directive to an integer. 84 | * 85 | * This method takes a string that represents a byte value in shorthand notation 86 | * (like '128M', '2G', or '1K') and converts it to an integer that represents the 87 | * same byte value. If the string does not end with 'K', 'M', or 'G', it is assumed 88 | * to be a byte value and is simply cast to an integer. 89 | * 90 | * @param string $value The shorthand byte value to convert. 91 | * @return int The byte value as an integer. 92 | */ 93 | private function convertToBytes(string $value): int 94 | { 95 | $value = trim($value); 96 | $last = strtolower($value[strlen($value) - 1]); 97 | $value = (int) $value; 98 | 99 | switch ($last) { 100 | case 'g': 101 | $value *= 1024; 102 | case 'm': 103 | $value *= 1024; 104 | case 'k': 105 | $value *= 1024; 106 | } 107 | 108 | return $value; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /resources/views/assets/styles.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Providers/MediableServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViews(); 20 | $this->loadMigrations(); 21 | $this->registerLivewireComponents(); 22 | $this->registerBladeComponents(); 23 | $this->registerPublishableResources(); 24 | $this->registerBladeDirectives(); 25 | } 26 | 27 | /** 28 | * Register bindings in the container. 29 | */ 30 | public function register(): void 31 | { 32 | $this->mergeConfig(); 33 | $this->registerCommands(); 34 | } 35 | 36 | /** 37 | * Load views. 38 | */ 39 | protected function loadViews(): void 40 | { 41 | $this->loadViewsFrom(__DIR__.'/../../resources/views', 'mediable'); 42 | $this->loadViewsFrom(__DIR__.'/../../resources/icons', 'icons'); 43 | } 44 | 45 | /** 46 | * Load migrations. 47 | */ 48 | protected function loadMigrations(): void 49 | { 50 | $this->loadMigrationsFrom(__DIR__.'/../../resources/database/migrations'); 51 | } 52 | 53 | /** 54 | * Register Livewire components. 55 | */ 56 | protected function registerLivewireComponents(): void 57 | { 58 | Livewire::component('mediable', MediaBrowser::class); 59 | } 60 | 61 | /** 62 | * Register Blade components. 63 | */ 64 | protected function registerBladeComponents(): void 65 | { 66 | Blade::component('mediable::scripts', Scripts::class); 67 | Blade::component('mediable::styles', Styles::class); 68 | 69 | Blade::component('icons::arrow', 'icons.arrow'); 70 | Blade::component('icons::close', 'icons.close'); 71 | Blade::component('icons::exit', 'icons.exit'); 72 | Blade::component('icons::logo', 'icons.logo'); 73 | } 74 | 75 | /** 76 | * Register publishable resources. 77 | */ 78 | protected function registerPublishableResources(): void 79 | { 80 | if ($this->app->runningInConsole()) { 81 | $this->publishes([ 82 | __DIR__.'/../../resources/config/config.php' => config_path('mediable.php'), 83 | ], 'mediable.config'); 84 | $this->publishes([ 85 | __DIR__.'/../../resources/views' => resource_path('views/vendor/mediable'), 86 | ], 'mediable.views'); 87 | $this->publishes([ 88 | __DIR__.'/../../resources/images' => public_path('vendor/mediable/images'), 89 | ], 'mediable.images'); 90 | $this->publishes([ 91 | __DIR__.'/../resources/fonts' => public_path('vendor/mediable/fonts'), 92 | ], 'mediable.fonts'); 93 | } 94 | } 95 | 96 | /** 97 | * Register Blade directives. 98 | */ 99 | protected function registerBladeDirectives(): void 100 | { 101 | Blade::directive('mediableStyles', function () { 102 | return "render(); ?>"; 103 | }); 104 | 105 | Blade::directive('mediableScripts', function () { 106 | return "render(); ?>"; 107 | }); 108 | } 109 | 110 | /** 111 | * Merge configuration. 112 | */ 113 | protected function mergeConfig(): void 114 | { 115 | $this->mergeConfigFrom(__DIR__.'/../../resources/config/config.php', 'mediable'); 116 | } 117 | 118 | /** 119 | * Register console commands. 120 | */ 121 | protected function registerCommands(): void 122 | { 123 | $this->commands([ 124 | InstallCommand::class, 125 | UpdateCommand::class, 126 | ]); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Concerns/ShowState.php: -------------------------------------------------------------------------------- 1 | showPagination; 29 | } 30 | 31 | public function isShowPerPage(): bool 32 | { 33 | return $this->showPerPage; 34 | } 35 | 36 | public function isShowOrderBy(): bool 37 | { 38 | return $this->showOrderBy; 39 | } 40 | 41 | public function isShowOrderDir(): bool 42 | { 43 | return $this->showOrderDir; 44 | } 45 | 46 | public function isShowColumnWidth(): bool 47 | { 48 | return $this->showColumnWidth; 49 | } 50 | 51 | public function isShowUniqueMimeTypes(): bool 52 | { 53 | return $this->showUniqueMimeTypes; 54 | } 55 | 56 | public function isShowSidebar(): bool 57 | { 58 | return $this->showSidebar; 59 | } 60 | 61 | public function isShowSearch(): bool 62 | { 63 | return $this->showSearch; 64 | } 65 | 66 | public function isShowUpload(): bool 67 | { 68 | return $this->showUpload; 69 | } 70 | 71 | public function isShowEditor(): bool 72 | { 73 | return $this->showEditor; 74 | } 75 | 76 | public function isShowPreview(): bool 77 | { 78 | return $this->showPreview; 79 | } 80 | 81 | public function isShowImageStrip(): bool 82 | { 83 | return $this->showImageStrip; 84 | } 85 | 86 | public function isShowMetaInfo(): bool 87 | { 88 | return $this->showMetaInfo; 89 | } 90 | 91 | public function isShowAppStats(): bool 92 | { 93 | return $this->showAppStats; 94 | } 95 | 96 | public function toLivewire() 97 | { 98 | return [ 99 | 'showPagination' => $this->showPagination, 100 | 'showPerPage' => $this->showPerPage, 101 | 'showOrderBy' => $this->showOrderBy, 102 | 'showOrderDir' => $this->showOrderDir, 103 | 'showColumnWidth' => $this->showColumnWidth, 104 | 'showUniqueMimeTypes' => $this->showUniqueMimeTypes, 105 | 'showSidebar' => $this->showSidebar, 106 | 'showSearch' => $this->showSearch, 107 | 'showUpload' => $this->showUpload, 108 | 'showEditor' => $this->showEditor, 109 | 'showPreview' => $this->showPreview, 110 | 'showImageStrip' => $this->showImageStrip, 111 | 'showMetaInfo' => $this->showMetaInfo, 112 | 'showAppStats' => $this->showAppStats, 113 | ]; 114 | } 115 | 116 | public static function fromLivewire($value) 117 | { 118 | return new self( 119 | showPagination: $value['showPagination'], 120 | showPerPage: $value['showPerPage'], 121 | showOrderBy: $value['showOrderBy'], 122 | showOrderDir: $value['showOrderDir'], 123 | showColumnWidth: $value['showColumnWidth'], 124 | showUniqueMimeTypes: $value['showUniqueMimeTypes'], 125 | showSidebar: $value['showSidebar'], 126 | showSearch: $value['showSearch'], 127 | showUpload: $value['showUpload'], 128 | showEditor: $value['showEditor'], 129 | showPreview: $value['showPreview'], 130 | showImageStrip: $value['showImageStrip'], 131 | showMetaInfo: $value['showMetaInfo'], 132 | showAppStats: $value['showAppStats'], 133 | ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /resources/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/meta.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 | @if ($this->mimeTypeImage($this->attachment->file_type)) 15 |
16 | 17 |
{{$this->attachment->title}}
18 |
19 | @endif 20 | 21 | @if ($this->mimeTypeVideo($this->attachment->file_type)) 22 |
23 | 24 |
{{$this->attachment->title}}
25 |
26 | @endif 27 | 28 | @if ($this->mimeTypeAudio($this->attachment->file_type)) 29 |
30 | 33 |
{{$this->attachment->title}}
34 |
35 | @endif 36 | 37 | @if ($this->attachment->file_original_name) 38 |
39 | {{ $this->attachment->file_original_name }} 40 |
41 | @endif 42 | 43 | @if ($this->attachment->file_size) 44 |
45 | {{ $this->formatBytes($this->attachment->file_size) }} 46 |
47 | @endif 48 | 49 | @if ($this->attachment->file_type) 50 |
51 | {{ $this->formatMimeType($this->attachment->file_type) }} 52 |
53 | @endif 54 | 55 | @if ($this->attachment->file_size && $this->imageWidth && $this->imageHeight) 56 |
57 | {{ $this->imageWidth }}×{{ $this->imageHeight }} 58 |
59 | @endif 60 | 61 | @if ($this->attachment->created_at) 62 |
63 | {{ $this->attachment->getCreatedAt() }} 64 |
65 | @endif 66 | 67 | @if ($this->attachment->updated_at) 68 |
69 | {{ $this->attachment->getUpdatedAt() }} 70 |
71 | @endif 72 | 73 |
74 |
75 |
76 | 77 |
78 |
79 |
80 |
-------------------------------------------------------------------------------- /resources/views/tailwind/includes/attachments.blade.php: -------------------------------------------------------------------------------- 1 | @if ($data->count()) 2 | 67 | @endif -------------------------------------------------------------------------------- /src/Concerns/AttachmentState.php: -------------------------------------------------------------------------------- 1 | id, 33 | file_name: $attachment->file_name, 34 | file_original_name: $attachment->file_original_name, 35 | file_type: $attachment->file_type, 36 | file_size: $attachment->file_size, 37 | file_dir: $attachment->file_dir, 38 | file_url: $attachment->file_url, 39 | title: $attachment->title, 40 | caption: $attachment->caption, 41 | description: $attachment->description, 42 | sort_order: $attachment->sort_order, 43 | styles: $attachment->styles, 44 | hidden: $attachment->hidden, 45 | created_at: $attachment->created_at, 46 | updated_at: $attachment->updated_at, 47 | ); 48 | } 49 | 50 | public function toLivewire() 51 | { 52 | return [ 53 | 'id' => $this->id, 54 | 'file_name' => $this->file_name, 55 | 'file_original_name' => $this->file_original_name, 56 | 'file_type' => $this->file_type, 57 | 'file_size' => $this->file_size, 58 | 'file_dir' => $this->file_dir, 59 | 'file_url' => $this->file_url, 60 | 'title' => $this->title, 61 | 'caption' => $this->caption, 62 | 'description' => $this->description, 63 | 'sort_order' => $this->sort_order, 64 | 'styles' => $this->styles, 65 | 'hidden' => $this->hidden, 66 | 'created_at' => $this->created_at, 67 | 'updated_at' => $this->updated_at, 68 | ]; 69 | } 70 | 71 | public static function fromLivewire($value) 72 | { 73 | return new self( 74 | id: $value['id'], 75 | file_name: $value['file_name'], 76 | file_original_name: $value['file_original_name'], 77 | file_type: $value['file_type'], 78 | file_size: $value['file_size'], 79 | file_dir: $value['file_dir'], 80 | file_url: $value['file_url'], 81 | title: $value['title'], 82 | caption: $value['caption'], 83 | description: $value['description'], 84 | sort_order: $value['sort_order'], 85 | styles: $value['styles'], 86 | hidden: $value['hidden'], 87 | created_at: $value['created_at'], 88 | updated_at: $value['updated_at'], 89 | ); 90 | } 91 | 92 | public function getId(): int 93 | { 94 | return $this->id; 95 | } 96 | 97 | public function getFileName(): string 98 | { 99 | return $this->file_name; 100 | } 101 | 102 | public function getOriginalFileName(): string 103 | { 104 | return $this->file_original_name; 105 | } 106 | 107 | public function getFileType(): string 108 | { 109 | return $this->file_type; 110 | } 111 | 112 | public function getFileSize(): int 113 | { 114 | return $this->file_size; 115 | } 116 | 117 | public function getFileDir(): string 118 | { 119 | return $this->file_dir; 120 | } 121 | 122 | public function getFileUrl(): string 123 | { 124 | return $this->file_url; 125 | } 126 | 127 | public function getTitle(): string 128 | { 129 | return $this->title; 130 | } 131 | 132 | public function getCaption(): string 133 | { 134 | return $this->caption; 135 | } 136 | 137 | public function getDescription(): string 138 | { 139 | return $this->description; 140 | } 141 | 142 | public function getSortOrder(): int 143 | { 144 | return $this->sort_order; 145 | } 146 | 147 | public function getStyles(): string 148 | { 149 | return $this->styles; 150 | } 151 | 152 | public function getCreatedAt(string $format = 'F j, Y, g:i a'): string 153 | { 154 | return Carbon::parse($this->created_at)->format($format); 155 | } 156 | 157 | public function getUpdatedAt(string $format = 'F j, Y, g:i a'): string 158 | { 159 | return Carbon::parse($this->updated_at)->format($format); 160 | } 161 | 162 | public function formatDateTime(string $value): string 163 | { 164 | return Carbon::parse($value)->format('F j, Y, g:i a'); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/GraphicDraw/GraphicDrawBase.php: -------------------------------------------------------------------------------- 1 | 'Horizontal', 11 | IMG_FLIP_VERTICAL => 'Vertical', 12 | IMG_FLIP_BOTH => 'Both', 13 | ]; 14 | 15 | public $filterModes = [ 16 | IMG_FILTER_NEGATE => 'Negate', 17 | IMG_FILTER_GRAYSCALE => 'Grayscale', 18 | IMG_FILTER_BRIGHTNESS => 'Brightness', 19 | IMG_FILTER_CONTRAST => 'Contrast', 20 | IMG_FILTER_COLORIZE => 'Colorize', 21 | IMG_FILTER_EDGEDETECT => 'Edge Detect', 22 | IMG_FILTER_EMBOSS => 'Emboss', 23 | IMG_FILTER_GAUSSIAN_BLUR => 'Gaussian Blur', 24 | IMG_FILTER_SELECTIVE_BLUR => 'Selective Blur', 25 | IMG_FILTER_MEAN_REMOVAL => 'Mean Removal', 26 | IMG_FILTER_SMOOTH => 'Smooth', 27 | IMG_FILTER_PIXELATE => 'Pixelate', 28 | ]; 29 | 30 | public $scaleModes = [ 31 | IMG_NEAREST_NEIGHBOUR => 'Nearest Neighbour', 32 | IMG_BILINEAR_FIXED => 'Bilinear Fixed', 33 | IMG_BICUBIC => 'Bicubic', 34 | ]; 35 | 36 | public function getFlipModes() 37 | { 38 | return $this->flipModes; 39 | } 40 | 41 | public function getFilterModes() 42 | { 43 | return $this->filterModes; 44 | } 45 | 46 | public function getScaleModes() 47 | { 48 | return $this->scaleModes; 49 | } 50 | 51 | public function create(string $filename): GdImage|false 52 | { 53 | return match (strtolower(pathinfo($filename, PATHINFO_EXTENSION))) { 54 | 'jpeg', 'jpg' => $this->imagecreatefromjpeg($filename), 55 | 'png' => $this->imagecreatefrompng($filename), 56 | 'gif' => $this->imagecreatefromgif($filename), 57 | 'webp' => $this->imagecreatefromwebp($filename), 58 | 'avif' => $this->imagecreatefromavif($filename), 59 | default => false, 60 | }; 61 | } 62 | 63 | public function save(string $filename, GdImage $image): bool 64 | { 65 | return match (strtolower(pathinfo($filename, PATHINFO_EXTENSION))) { 66 | 'jpeg', 'jpg' => $this->imagejpeg($image, $filename), 67 | 'png' => $this->imagepng($image, $filename), 68 | 'gif' => $this->imagegif($image, $filename), 69 | 'webp' => $this->imagewebp($image, $filename), 70 | 'avif' => $this->imageavif($image, $filename), 71 | default => false, 72 | }; 73 | } 74 | 75 | public function imagecreatefrompath(string $filename): GdImage|false 76 | { 77 | return $this->create($filename); 78 | } 79 | 80 | public function imagecreatefromstring(string $data): GdImage|false 81 | { 82 | return imagecreatefromstring($data); 83 | } 84 | 85 | public function imagecreatefromjpeg(string $filename): GdImage|false 86 | { 87 | return imagecreatefromjpeg($filename); 88 | } 89 | 90 | public function imagecreatefrompng(string $filename): GdImage|false 91 | { 92 | return imagecreatefrompng($filename); 93 | } 94 | 95 | public function imagecreatefromgif(string $filename): GdImage|false 96 | { 97 | return imagecreatefromgif($filename); 98 | } 99 | 100 | public function imagecreatefromwebp(string $filename): GdImage|false 101 | { 102 | return imagecreatefromwebp($filename); 103 | } 104 | 105 | public function imagecreatefromavif(string $filename): GdImage|false 106 | { 107 | return imagecreatefromavif($filename); 108 | } 109 | 110 | public function imagewebp(GdImage $image, $file = null, int $quality = -1): bool 111 | { 112 | return imagewebp($image, $file, $quality); 113 | } 114 | 115 | public function imageavif(GdImage $image, $file = null, int $quality = -1): bool 116 | { 117 | return imageavif($image, $file, $quality); 118 | } 119 | 120 | public function imagejpeg(GdImage $image, $file = null, int $quality = -1): bool 121 | { 122 | return imagejpeg($image, $file, $quality); 123 | } 124 | 125 | public function imagepng(GdImage $image, $file = null, int $quality = -1, int $filters = -1): bool 126 | { 127 | return imagepng($image, $file, $quality, $filters); 128 | } 129 | 130 | public function imagegif(GdImage $image, $file = null): bool 131 | { 132 | return imagegif($image, $file); 133 | } 134 | 135 | public function imagedestroy(GdImage $image): bool 136 | { 137 | return imagedestroy($image); 138 | } 139 | 140 | public function getimagesize(string $filename, array $image_info = []): array|false 141 | { 142 | return getimagesize($filename, $image_info); 143 | } 144 | 145 | public function imagesy(GdImage $image): int|false 146 | { 147 | return imagesy($image); 148 | } 149 | 150 | public function imagesx(GdImage $image): int|false 151 | { 152 | return imagesx($image); 153 | } 154 | 155 | public function getImageMimeType(int $imageType): ?string 156 | { 157 | return image_type_to_mime_type($imageType); 158 | } 159 | 160 | public function getImageExtension(int $imageType): ?string 161 | { 162 | return image_type_to_extension($imageType); 163 | } 164 | 165 | public function scale(GdImage $image, int $new_width, int $new_height = -1, int $mode = IMG_BILINEAR_FIXED): GdImage|false 166 | { 167 | return imagescale($image, $new_width, $new_height, $mode); 168 | } 169 | 170 | public function crop(GdImage $image, array $rect): GdImage 171 | { 172 | return imagecrop($image, $rect); 173 | } 174 | 175 | public function flip(GdImage $image, int $mode): bool 176 | { 177 | return imageflip($image, $mode); 178 | } 179 | 180 | public function rotate(GdImage $image, float $angle, int $bgd_color, bool $ignore_transparent = false): GdImage|false 181 | { 182 | return imagerotate($image, $angle, $bgd_color, $ignore_transparent); 183 | } 184 | 185 | public function filter(GdImage $image, int $filter, array $args): bool 186 | { 187 | return imagefilter($image, $filter, ...$args); 188 | } 189 | 190 | public function text(GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $fontfile, string $text): array|false 191 | { 192 | return imagettftext($image, $size, $angle, $x, $y, $color, $fontfile, $text); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/pagination.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if ($paginator->hasPages()) 3 | 115 | @endif 116 |
-------------------------------------------------------------------------------- /resources/views/tailwind/includes/uploads.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if (sizeof($files)) 4 |
5 |
6 | @if(count($files) >= 1) 7 |
8 | {{ count($files) }} files selected 9 | {{ $this->formatBytes($this->getTotalUploadSize()) }} upload size 10 |
11 | @endif 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | @foreach($files as $index => $file) 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 42 | 43 | @endforeach 44 | 45 |
IdNameTypeSizeActions
{{ $index+1 }}{!! \Illuminate\Support\Str::limit($file->getClientOriginalName(), 40, '...') !!}{{ $file->getMimeType() }}{{$this->formatBytes($file->getSize())}} 35 | 36 | 40 | 41 |
46 |
47 |
48 |
49 | @else 50 |
51 |
52 |
53 | 54 | Upload files 55 | 56 |
57 |

Drop files here or click to upload.

58 | Upload up to files not to exceed . Maximum single file size is . 59 |
60 |
61 |
62 | 65 |
66 | @endif 67 | 68 |
69 | 70 | @script 71 | 183 | @endscript -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mediable 🎥📸🎵📂 2 | 3 | Mediable is a light weight easy to use Laravel Livewire Media Manager. Mediable is awesome for injecting content into blog posts, carousels, product previews or similar applications. 4 | 5 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/tomshaw/mediable/run-tests.yml?branch=master&style=flat-square&label=tests) 6 | ![issues](https://img.shields.io/github/issues/tomshaw/mediable?style=flat&logo=appveyor) 7 | ![forks](https://img.shields.io/github/forks/tomshaw/mediable?style=flat&logo=appveyor) 8 | ![stars](https://img.shields.io/github/stars/tomshaw/mediable?style=flat&logo=appveyor) 9 | [![GitHub license](https://img.shields.io/github/license/tomshaw/mediable)](https://github.com/tomshaw/mediable/blob/master/LICENSE) 10 | 11 | > For a complete demonstration see the [Mediable Demo](https://github.com/tomshaw/mediable-demo) repository. 12 | 13 | #### Screenshot 14 | ![Mediable](https://raw.github.com/tomshaw/mediable/master/docs/mediable.jpg) 15 | 16 | ## Features 17 | 18 | - **Easy Integration**: Seamlessly integrate Mediable into your Laravel Livewire applications. 19 | - **File Uploads**: Supports uploading various file types including images, videos, and documents. 20 | - **Image Conversion**: Automatically create WebP and AVIF versions of your image uploads. 21 | - **Customizable**: Configure allowable file types, maximum file size, storage disk, and folder. 22 | - **SEO-Friendly**: Generate SEO-friendly file names for your uploads. 23 | - **Event Handling**: Dispatch events to handle selected attachments and other actions. 24 | - **Database Integration**: Store upload information in the database with the included migration. 25 | - **Graphic Draw**: Perform various image manipulations using the GD Library. 26 | 27 | ## Installation 28 | 29 | You can install the package via composer: 30 | 31 | ```bash 32 | composer require tomshaw/mediable 33 | ``` 34 | 35 | Mediable comes with both install and update commands. 36 | 37 | ```bash 38 | php artisan mediable:install 39 | ``` 40 | 41 | ```bash 42 | php artisan mediable:update 43 | ``` 44 | 45 | Run the included database migration. 46 | 47 | > This creates an attachments table that stores upload information. 48 | 49 | ```bash 50 | php artisan migrate 51 | ``` 52 | 53 | Add Mediable styles and scripts directives to your layout. 54 | 55 | ```html 56 | @vite(['resources/css/app.css', 'resources/js/app.js']) 57 | 58 | @mediableStyles 59 | @mediableScripts 60 | ``` 61 | 62 | Make sure your `.env` `APP_URL` is correctly set. 63 | 64 | ```env 65 | APP_URL=https://mydomain.com 66 | ``` 67 | 68 | Finally make uploaded files accessible from the web. 69 | 70 | ```bash 71 | php artisan storage:link 72 | ``` 73 | 74 | ## Usage 75 | 76 | Add the Mediable component to your blade template. 77 | 78 | > Boolean options can be provided by only specifying the key. 79 | 80 | ```html 81 | 82 | ``` 83 | 84 | Launching Mediable is done by dispatching the `mediable.open` event. 85 | 86 | > This is typically executed with a button click. 87 | 88 | ```php 89 | $this->dispatch('mediable.open'); 90 | ``` 91 | 92 | Insert attachments directly into form inputs using PHP 8 named parameters. 93 | 94 | > This example launches the modal with the intention of injecting attachments directly into an html input that has an `id` of `description`. 95 | 96 | ```php 97 | $this->dispatch('mediable.open', id: 'description'); 98 | ``` 99 | 100 | Use the `mediable.on` event to handle selected attachments. 101 | 102 | ```php 103 | on(['mediable.on' => function ($files) { 104 | // Handle selected files... 105 | }]); 106 | ``` 107 | 108 | ## Validation 109 | 110 | You can customize allowable file types and max file size in the `mediable.php` config file. 111 | 112 | ```php 113 | 'validation' => [ 114 | 'files.*' => 'required|mimes:jpeg,png,jpg,gif,mp3,mp4,m4a,ogg,wav,webm,avi,mov,wmv,txt,pdf,doc,docx,xls,xlsx,ppt,pptx,zip,rar|max:10240', 115 | ], 116 | ``` 117 | 118 | The `mimes` rule specifies the allowable file types. To add a new file type, simply add its mime type to the list. For example, to allow SVG files, you would change it to: 119 | 120 | ```php 121 | 'mimes:jpeg,png,jpg,gif,mp3,mp4,m4a,ogg,wav,webm,avi,mov,wmv,txt,pdf,doc,docx,xls,xlsx,ppt,pptx,zip,rar,svg' 122 | ``` 123 | 124 | The `max` rule specifies the maximum file size, in kilobytes. To change the maximum file size, simply change the number. For example, to allow files up to 50MB, you would change it to: 125 | 126 | ```php 127 | 'max:51200' 128 | ``` 129 | 130 | ## Storage Disk 131 | 132 | You can configure the storage disk used for file uploads in the `mediable.php` config file. The `disk` option is used to specify the disk name: 133 | 134 | ```php 135 | 'disk' => env('MEDIABLE_DISK_DRIVER', 'public'), 136 | ``` 137 | 138 | The value of `disk` is the key of `disks` in your Laravel application's `config/filesystems.php` file. By default, it uses the disk specified by the `FILESYSTEM_DRIVER` environment variable, or 'public' if the environment variable is not set. 139 | 140 | You can change the `disk` option to use a different disk for file uploads. For example, to use the 's3' disk, you can set `disk` to 's3': 141 | 142 | ```php 143 | 'disk' => 's3', 144 | ``` 145 | 146 | Remember to configure the chosen disk correctly in your `config/filesystems.php` file and to clear your config cache after making changes by running `php artisan config:clear` in your terminal. 147 | 148 | ## Disk Folder 149 | 150 | You can configure the folder used for file uploads on the specified disk. This configuration allows you to organize and manage and your uploaded files. 151 | 152 | ```php 153 | 'folder' => env('MEDIABLE_DISK_FOLDER', 'uploads'), 154 | ``` 155 | 156 | ## GD Library Graphic Draw 157 | 158 | Mediable includes a set of tools for performing various image manipulations using the GD Library. Here are some of the things you can do: 159 | 160 | - **Flip Image**: Flip the image horizontally or vertically. 161 | - **Scale Image**: Resize the image while maintaining the aspect ratio. 162 | - **Apply Filters**: Apply various filters such as contrast, brightness, colorize, smooth, and pixelate. 163 | - **Rotate Image**: Rotate the image by a specified angle with a background color. 164 | - **Crop Image**: Crop the image to a specified rectangle. 165 | - **Add Text**: Add text to the image with specified font, size, color, and angle. 166 | 167 | ## Image Conversion Settings 168 | 169 | Mediable can automatically create WebP and AVIF versions of your image uploads. You can control this behavior with the following environment variables: 170 | 171 | - `MEDIABLE_CREATE_WEBP`: Set this to `true` to create a WebP version of each image upload, or `false` to disable this feature. By default, this is set to `true`. 172 | 173 | - `MEDIABLE_CREATE_AVIF`: Set this to `true` to create an AVIF version of each image upload, or `false` to disable this feature. By default, this is set to `true`. 174 | 175 | You can also control the quality of the WebP and AVIF versions with the following environment variables: 176 | 177 | - `MEDIABLE_WEBP_QUALITY`: Set this to any integer between 0 and 100 to control the quality of the WebP versions. A higher number means better quality but larger file size. By default, this is set to 80. 178 | 179 | - `MEDIABLE_AVIF_QUALITY`: Set this to any integer between 0 and 100 to control the quality of the AVIF versions. A higher number means better quality but larger file size. By default, this is set to 80. 180 | 181 | Here's an example of how you might set these environment variables in your `.env` file: 182 | 183 | ```env 184 | MEDIABLE_CREATE_WEBP=true 185 | MEDIABLE_CREATE_AVIF=true 186 | MEDIABLE_WEBP_QUALITY=80 187 | MEDIABLE_AVIF_QUALITY=80 188 | ``` 189 | 190 | ## Contributing 191 | 192 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 193 | 194 | ## License 195 | 196 | The MIT License (MIT). See [License File](LICENSE) for more information. 197 | 198 | ``` 199 | 200 | _ _ _ _ 201 | /\/\ ___ __| (_) __ _| |__ | | ___ 202 | / \ / _ \/ _` | |/ _` | '_ \| |/ _ \ 203 | / /\/\ \ __/ (_| | | (_| | |_) | | __/ 204 | \/ \/\___|\__,_|_|\__,_|_.__/|_|\___| 205 | 206 | ``` 207 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/toolbar.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if($panel->isUploadMode()) 4 |
5 | @if(!$data->isEmpty()) 6 | 9 | @endif 10 |
11 | @if(count($files) >= 1) 12 |
13 | 17 | 22 |
23 | @endif 24 | @endif 25 | 26 | @if($panel->isThumbMode()) 27 |
28 | 29 | 32 | 33 | @if($show->isShowOrderBy() && $data->count()) 34 | 39 | @endif 40 | 41 | @if($show->isShowOrderDir() && $data->count()) 42 | 54 | @endif 55 | 56 | @if($show->isShowColumnWidth() && $data->count()) 57 | 62 | @endif 63 | 64 | @if($show->isShowUniqueMimeTypes() && count($uniqueMimeTypes)>=1) 65 | 71 | @endif 72 | 73 |
74 |
75 | 76 | @if ($show->isShowEditor() && count($selected)) 77 | 82 | @endif 83 | 84 | @if ($show->isShowUpload()) 85 | 92 | @endif 93 | 94 | 97 |
98 | @endif 99 | 100 | @if($panel->isPreviewMode()) 101 |
102 | 103 | 106 | 107 | 110 | 111 | 114 | 115 |
116 |
117 | 118 | 122 | 123 | @if ($show->isShowEditor() && count($selected)) 124 | 129 | @endif 130 | 131 | 134 |
135 | @endif 136 | 137 | @if($panel->isEditorMode()) 138 |
139 | 142 | 143 | 146 | 147 | 150 |
151 |
152 | 155 |
156 | @endif 157 | 158 |
-------------------------------------------------------------------------------- /src/Traits/WithGraphicDraw.php: -------------------------------------------------------------------------------- 1 | 'Flip Image', 68 | 'image-scale' => 'Scale Image', 69 | 'image-filter' => 'Filter Image', 70 | 'image-rotate' => 'Rotate Image', 71 | 'image-crop' => 'Crop Image', 72 | 'image-text' => 'Add Text', 73 | ]; 74 | 75 | public function setForm(string $key): void 76 | { 77 | $this->selectedForm = $key; 78 | } 79 | 80 | public function resetForm(): void 81 | { 82 | $this->selectedForm = ''; 83 | } 84 | 85 | public function getFlipModes(): array 86 | { 87 | return GraphicDraw::getFlipModes(); 88 | } 89 | 90 | public function getFilterModes(): array 91 | { 92 | return GraphicDraw::getFilterModes(); 93 | } 94 | 95 | public function getScaleModes(): array 96 | { 97 | return GraphicDraw::getScaleModes(); 98 | } 99 | 100 | public function getImageSize(string $filename, array $image_info = []): array|false 101 | { 102 | return GraphicDraw::getimagesize($filename, $image_info); 103 | } 104 | 105 | public function getImageMimeType(int $imageType): ?string 106 | { 107 | return GraphicDraw::getImageMimeType($imageType); 108 | } 109 | 110 | public function getImageExtension(int $imageType): ?string 111 | { 112 | return GraphicDraw::getImageExtension($imageType); 113 | } 114 | 115 | public function getDiskImagePath() 116 | { 117 | $disk = config('mediable.disk'); 118 | 119 | return Storage::disk($disk)->path($this->attachment->file_dir); 120 | } 121 | 122 | public function updatedScaleWidth() 123 | { 124 | $image = GraphicDraw::imagecreatefrompath($this->getDiskImagePath()); 125 | 126 | $originalWidth = GraphicDraw::imagesx($image); 127 | $originalHeight = GraphicDraw::imagesy($image); 128 | 129 | $aspectRatio = $originalWidth / $originalHeight; 130 | 131 | $this->scaleHeight = intval($this->scaleWidth / $aspectRatio); 132 | } 133 | 134 | public function updatedScaleHeight() 135 | { 136 | $image = GraphicDraw::imagecreatefrompath($this->getDiskImagePath()); 137 | 138 | $originalWidth = GraphicDraw::imagesx($image); 139 | $originalHeight = GraphicDraw::imagesy($image); 140 | 141 | $aspectRatio = $originalWidth / $originalHeight; 142 | 143 | $this->scaleWidth = intval($this->scaleHeight * $aspectRatio); 144 | } 145 | 146 | public function flipImage() 147 | { 148 | if (! $this->flipMode) { 149 | return; 150 | } 151 | 152 | GraphicDraw::flipAndSave($this->getDiskImagePath(), $this->flipMode); 153 | 154 | $this->generateUniqueId(); 155 | 156 | $this->editHistory[] = $this->getDrawSettings(); 157 | } 158 | 159 | public function scaleImage() 160 | { 161 | if (! $this->scaleMode) { 162 | return; 163 | } 164 | 165 | GraphicDraw::scaleAndSave($this->getDiskImagePath(), $this->scaleWidth, $this->scaleHeight, $this->scaleMode); 166 | 167 | $this->generateUniqueId(); 168 | 169 | $this->editHistory[] = $this->getDrawSettings(); 170 | } 171 | 172 | public function filterImage() 173 | { 174 | if (! $this->filterMode) { 175 | return; 176 | } 177 | 178 | $this->normalizeColors(); 179 | 180 | $args = []; 181 | if ($this->filterMode == IMG_FILTER_CONTRAST) { 182 | $args[] = $this->contrast; 183 | } elseif ($this->filterMode == IMG_FILTER_BRIGHTNESS) { 184 | $args[] = $this->brightness; 185 | } elseif ($this->filterMode == IMG_FILTER_COLORIZE) { 186 | $args[] = $this->colorizeRed; 187 | $args[] = $this->colorizeGreen; 188 | $args[] = $this->colorizeBlue; 189 | } elseif ($this->filterMode == IMG_FILTER_SMOOTH) { 190 | $args[] = $this->smoothLevel; 191 | } elseif ($this->filterMode == IMG_FILTER_PIXELATE) { 192 | $args[] = $this->pixelateBlockSize; 193 | } 194 | 195 | GraphicDraw::filterAndSave($this->getDiskImagePath(), $this->filterMode, $args); 196 | 197 | $this->generateUniqueId(); 198 | 199 | $this->editHistory[] = $this->getDrawSettings(); 200 | } 201 | 202 | public function rotateImage() 203 | { 204 | if (! $this->rotateAngle) { 205 | return; 206 | } 207 | 208 | $backgroundColor = $this->normalizeHexValue($this->rotateBgColor); 209 | 210 | GraphicDraw::rotateAndSave($this->getDiskImagePath(), $this->rotateAngle, $backgroundColor, $this->rotateIgnoreTransparent); 211 | 212 | $this->generateUniqueId(); 213 | 214 | $this->editHistory[] = $this->getDrawSettings(); 215 | } 216 | 217 | public function cropImage() 218 | { 219 | $rect = ['x' => $this->cropX, 'y' => $this->cropY, 'width' => $this->cropWidth, 'height' => $this->cropHeight]; 220 | 221 | GraphicDraw::cropAndSave($this->getDiskImagePath(), $rect); 222 | 223 | $this->generateUniqueId(); 224 | 225 | $this->editHistory[] = $this->getDrawSettings(); 226 | } 227 | 228 | public function addText() 229 | { 230 | if (! is_numeric($this->imageFontSize) || $this->imageFontSize <= 0) { 231 | return; 232 | } 233 | 234 | if (! file_exists($this->imageFont) || ! is_readable($this->imageFont)) { 235 | return; 236 | } 237 | 238 | if (! is_string($this->imageText) || trim($this->imageText) === '') { 239 | return; 240 | } 241 | 242 | if (! is_numeric($this->imageTextAngle) || $this->imageTextAngle < 0 || $this->imageTextAngle > 360) { 243 | return; 244 | } 245 | 246 | $color = $this->normalizeHexValue($this->imageTextColor); 247 | 248 | $centered = $this->centerText(); 249 | 250 | GraphicDraw::textAndSave($this->getDiskImagePath(), $this->imageFontSize, $this->imageTextAngle, $centered[0], $centered[1], $color, $this->imageFont, $this->imageText); 251 | 252 | $this->generateUniqueId(); 253 | 254 | $this->editHistory[] = $this->getDrawSettings(); 255 | } 256 | 257 | public function normalizeColors() 258 | { 259 | [$r, $g, $b] = sscanf($this->colorize, '#%02x%02x%02x'); 260 | 261 | $this->colorizeRed = $r - 255; 262 | $this->colorizeGreen = $g - 255; 263 | $this->colorizeBlue = $b - 255; 264 | } 265 | 266 | public function normalizeHexValue(string $hexColor) 267 | { 268 | $hexColor = ltrim($hexColor, '#'); 269 | 270 | $red = hexdec(substr($hexColor, 0, 2)); 271 | $green = hexdec(substr($hexColor, 2, 2)); 272 | $blue = hexdec(substr($hexColor, 4, 2)); 273 | 274 | $image = imagecreatetruecolor(100, 100); 275 | 276 | return imagecolorallocate($image, $red, $green, $blue); 277 | } 278 | 279 | public function centerText() 280 | { 281 | [$imageWidth, $imageHeight] = getimagesize($this->getDiskImagePath()); 282 | 283 | $bbox = imagettfbbox($this->imageFontSize, $this->imageTextAngle, $this->imageFont, $this->imageText); 284 | 285 | $textWidth = $bbox[2] - $bbox[0]; 286 | $textHeight = $bbox[7] - $bbox[1]; 287 | 288 | $x = ($imageWidth / 2) - ($textWidth / 2); 289 | $y = ($imageHeight / 2) - ($textHeight / 2); 290 | 291 | return [$x, $y]; 292 | } 293 | 294 | public function getDrawSettings(): array 295 | { 296 | return [ 297 | 'flipMode' => $this->flipMode, 298 | 'filterMode' => $this->filterMode, 299 | 'contrast' => $this->contrast, 300 | 'brightness' => $this->brightness, 301 | 'colorize' => $this->colorize, 302 | 'colorizeRed' => $this->colorizeRed, 303 | 'colorizeGreen' => $this->colorizeGreen, 304 | 'colorizeBlue' => $this->colorizeBlue, 305 | 'smoothLevel' => $this->smoothLevel, 306 | 'pixelateBlockSize' => $this->pixelateBlockSize, 307 | 'scaleWidth' => $this->scaleWidth, 308 | 'scaleHeight' => $this->scaleHeight, 309 | 'scaleMode' => $this->scaleMode, 310 | 'rotateAngle' => $this->rotateAngle, 311 | 'rotateBgColor' => $this->rotateBgColor, 312 | 'rotateIgnoreTransparent' => $this->rotateIgnoreTransparent, 313 | 'cropX' => $this->cropX, 314 | 'cropY' => $this->cropY, 315 | 'cropWidth' => $this->cropWidth, 316 | 'cropHeight' => $this->cropHeight, 317 | 'imageText' => $this->imageText, 318 | 'imageFont' => $this->imageFont, 319 | 'imageFontSize' => $this->imageFontSize, 320 | 'imageTextColor' => $this->imageTextColor, 321 | 'imageTextAngle' => $this->imageTextAngle, 322 | 'primaryId' => $this->primaryId, 323 | ]; 324 | } 325 | 326 | public function fillEditorProperties() 327 | { 328 | $this->fill([ 329 | 'flipMode' => null, 330 | 'filterMode' => null, 331 | 'contrast' => 0, 332 | 'brightness' => 0, 333 | 'colorize' => '', 334 | 'colorizeRed' => -50, 335 | 'colorizeGreen' => -50, 336 | 'colorizeBlue' => 50, 337 | 'smoothLevel' => 0, 338 | 'pixelateBlockSize' => 1, 339 | 'scaleWidth' => 0, 340 | 'scaleHeight' => 0, 341 | 'scaleMode' => 4, 342 | 'rotateAngle' => 0, 343 | 'rotateBgColor' => '#000000', 344 | 'rotateIgnoreTransparent' => false, 345 | 'cropX' => 0, 346 | 'cropY' => 0, 347 | 'cropWidth' => 0, 348 | 'cropHeight' => 0, 349 | 'imageText' => '', 350 | 'imageFont' => '', 351 | 'imageFontSize' => 42.0, 352 | 'imageTextColor' => '#000000', 353 | 'imageTextAngle' => 0, 354 | ]); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /resources/views/tailwind/media-browser.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
!$fullScreen, 'rounded-none shadow-none top-0 left-0 right-0 bottom-0' => $fullScreen])> 6 |
7 |
8 | 9 |
10 | @include("mediable::tailwind.includes.header") 11 |
12 | 13 |
14 | 15 | @if($show->isShowMetaInfo() && !$panel->isUploadMode() && !$data->isEmpty()) 16 |
17 |
18 | @if(!$panel->isEditorMode()) 19 | @if(count($selected)) 20 | @include("mediable::tailwind.includes.meta") 21 | @elseif($show->isShowAppStats()) 22 | @include("mediable::tailwind.includes.stats") 23 | @endif 24 | @elseif($panel->isEditorMode()) 25 | @include("mediable::tailwind.includes.form") 26 | @endif 27 |
28 |
29 | @endif 30 | 31 |
32 |
33 | 34 |
35 | @include("mediable::tailwind.includes.toolbar") 36 | @include("mediable::tailwind.includes.alert") 37 |
38 | 39 |
40 |
41 |
42 |
$panel->isThumbMode()])> 43 | @include("mediable::tailwind.includes.attachments") 44 |
45 |
$panel->isPreviewMode()])> 46 | @include("mediable::tailwind.includes.previews") 47 |
48 |
$panel->isEditorMode()])> 49 | @include("mediable::tailwind.includes.editor") 50 |
51 |
$panel->isUploadMode()])> 52 | @include("mediable::tailwind.includes.uploads") 53 |
54 |
55 |
56 |
57 | 58 | @if(!$panel->isUploadMode() && !$panel->isEditorMode()) 59 |
60 |
61 | @if($show->isShowPagination() && method_exists($data, 'links')) 62 | {!! $data->links("mediable::tailwind.includes.pagination") !!} 63 | @endif 64 | @if($show->isShowPerPage() && method_exists($data, 'links') && $data->hasPages()) 65 | 72 | @endif 73 |
74 |
75 | @endif 76 | 77 |
78 |
79 | 80 | @if($show->isShowSidebar() && !$panel->isUploadMode() && !$data->isEmpty()) 81 |
82 | @include("mediable::tailwind.includes.sidebar") 83 |
84 | @endif 85 | 86 |
87 | 88 | @if($show->isShowImageStrip() && !$panel->isUploadMode() && !$panel->isEditorMode() && !$data->isEmpty()) 89 | 94 | @endif 95 | 96 | @if(!$panel->isUploadMode() && !$panel->isEditorMode() && count($this->selected) && !$data->isEmpty()) 97 |
98 | @include("mediable::tailwind.includes.footer") 99 |
100 | @endif 101 | 102 |
103 |
104 |
105 |
106 | 107 | @script 108 | 221 | @endscript -------------------------------------------------------------------------------- /src/Eloquent/EloquentManager.php: -------------------------------------------------------------------------------- 1 | query = Attachment::where('hidden', '=', false)->where('file_type', '=', $mimeType)->orderBy($orderBy, $orderDir); 32 | } else { 33 | $this->query = Attachment::where('hidden', '=', false)->orderBy($orderBy, $orderDir); 34 | } 35 | } 36 | 37 | public function create(array $files): void 38 | { 39 | $diskConfig = $this->getAndValidateDisk(config('mediable.disk')); 40 | 41 | $disk = $diskConfig['disk']; 42 | 43 | $folder = config('mediable.folder'); 44 | 45 | foreach ($files as $file) { 46 | if (is_null($file)) { 47 | continue; 48 | } 49 | 50 | $fileName = $this->prepareFileName($file->getClientOriginalName()); 51 | 52 | $storagePath = $file->storePubliclyAs(path: $folder, name: $fileName, options: $disk); 53 | 54 | $fullPath = Storage::disk($disk)->path($storagePath); 55 | 56 | $data = $this->createDataArray($file, $storagePath, $fileName); 57 | 58 | try { 59 | Attachment::create($data); 60 | } catch (Exception $e) { 61 | throw new MediaBrowserException($e->getMessage()); 62 | } 63 | 64 | if (str_starts_with($file->getMimeType(), 'image/')) { 65 | 66 | try { 67 | $image = imagecreatefromstring(file_get_contents($fullPath)); 68 | } catch (Exception $e) { 69 | continue; 70 | } 71 | 72 | if (config('mediable.create_webp')) { 73 | 74 | try { 75 | $path = $this->createImageResource($image, $storagePath, $disk, 'image/webp', config('mediable.webp_quality')); 76 | } catch (Exception $e) { 77 | continue; 78 | } 79 | 80 | $create = $this->createDataArray($file, $path, $fileName); 81 | 82 | $create['file_type'] = 'image/webp'; 83 | 84 | if (Storage::disk($disk)->exists($path)) { 85 | $create['file_dir'] = $storagePath; 86 | $create['title'] = pathinfo($path, PATHINFO_FILENAME); 87 | $create['file_name'] = pathinfo($path, PATHINFO_BASENAME); 88 | $create['file_original_name'] = pathinfo($path, PATHINFO_BASENAME); 89 | $create['file_size'] = Storage::disk($disk)->size($path); 90 | } 91 | 92 | Attachment::create($create); 93 | } 94 | 95 | if (config('mediable.create_avif')) { 96 | 97 | try { 98 | $path = $this->createImageResource($image, $storagePath, $disk, 'image/avif', config('mediable.avif_quality')); 99 | } catch (Exception $e) { 100 | continue; 101 | } 102 | 103 | $create = $this->createDataArray($file, $path, $fileName); 104 | 105 | $create['file_type'] = 'image/avif'; 106 | 107 | if (Storage::disk($disk)->exists($path)) { 108 | $create['file_dir'] = $storagePath; 109 | $create['title'] = pathinfo($path, PATHINFO_FILENAME); 110 | $create['file_name'] = pathinfo($path, PATHINFO_BASENAME); 111 | $create['file_original_name'] = pathinfo($path, PATHINFO_BASENAME); 112 | $create['file_size'] = Storage::disk($disk)->size($path); 113 | } 114 | 115 | Attachment::create($create); 116 | } 117 | 118 | imagedestroy($image); 119 | } 120 | } 121 | } 122 | 123 | private function createDataArray(TemporaryUploadedFile $file, string $storagePath, string $fileName): array 124 | { 125 | return [ 126 | 'file_name' => $fileName, 127 | 'file_original_name' => $file->getClientOriginalName(), 128 | 'file_type' => $file->getMimeType(), 129 | 'file_size' => $file->getSize(), 130 | 'file_dir' => $storagePath, 131 | 'file_url' => asset('storage/'.$storagePath), 132 | 'title' => pathinfo($fileName, PATHINFO_FILENAME), 133 | ]; 134 | } 135 | 136 | private function createImageResource(GdImage $image, string $stored, string $disk, string $type = 'image/webp', int $quality = -1) 137 | { 138 | $extension = ($type === 'image/webp') ? 'webp' : 'avif'; 139 | 140 | $directory = config('mediable.folder'); 141 | $baseName = pathinfo($stored, PATHINFO_FILENAME); 142 | $path = $directory.'/'.$baseName.'.'.$extension; 143 | 144 | ob_start(); 145 | if ($type === 'image/webp') { 146 | imagewebp($image, null, $quality); 147 | } else { 148 | imageavif($image, null, $quality); 149 | } 150 | $content = ob_get_clean(); 151 | 152 | Storage::disk($disk)->put($path, $content); 153 | 154 | return $path; 155 | } 156 | 157 | public function update(int $id, array $data = []): void 158 | { 159 | try { 160 | Attachment::where('id', $id)->update($data); 161 | } catch (Exception $e) { 162 | throw new MediaBrowserException($e->getMessage()); 163 | } 164 | } 165 | 166 | public function enable(int $id): void 167 | { 168 | try { 169 | Attachment::where('id', $id)->update([ 170 | 'hidden' => false, 171 | ]); 172 | } catch (Exception $e) { 173 | throw new MediaBrowserException($e->getMessage()); 174 | } 175 | } 176 | 177 | public function delete(int $id): void 178 | { 179 | try { 180 | Attachment::find($id)->delete(); 181 | } catch (Exception $e) { 182 | throw new MediaBrowserException($e->getMessage()); 183 | } 184 | } 185 | 186 | public function garbage(): void 187 | { 188 | try { 189 | $attachments = Attachment::where('hidden', true)->get(); 190 | 191 | foreach ($attachments as $attachment) { 192 | $fileDir = $attachment->file_dir; 193 | 194 | if (Storage::exists($fileDir)) { 195 | Storage::delete($fileDir); 196 | } 197 | 198 | $attachment->delete(); 199 | } 200 | } catch (Exception $e) { 201 | throw new MediaBrowserException($e->getMessage()); 202 | } 203 | } 204 | 205 | public function copyImageFromTo(string $source, string $destination): string 206 | { 207 | $diskConfig = $this->getAndValidateDisk(config('mediable.disk')); 208 | 209 | $disk = $diskConfig['disk']; 210 | 211 | if (! Storage::disk($disk)->exists($source)) { 212 | throw new MediaBrowserException('Source file not found.'); 213 | } 214 | 215 | if (Storage::disk($disk)->exists($destination)) { 216 | throw new MediaBrowserException('Destination file already exists.'); 217 | } 218 | 219 | try { 220 | Storage::disk($disk)->copy($source, $destination); 221 | } catch (Exception $e) { 222 | throw new MediaBrowserException($e->getMessage()); 223 | } 224 | 225 | return $destination; 226 | } 227 | 228 | public function saveImageToDatabase(AttachmentState $attachment, string $destination): Attachment 229 | { 230 | $diskConfig = $this->getAndValidateDisk(config('mediable.disk')); 231 | 232 | $disk = $diskConfig['disk']; 233 | 234 | $filePath = Storage::disk($disk)->path($destination); 235 | 236 | $file = $this->fileObject($filePath); 237 | 238 | $create = [ 239 | 'file_name' => $file->getFilename(), 240 | 'file_original_name' => $file->getFilename(), 241 | 'file_type' => $file->getMimeType(), 242 | 'file_size' => $file->getSize(), 243 | 'file_dir' => $destination, 244 | 'file_url' => asset('storage/'.$destination), 245 | 'title' => $attachment->title, 246 | 'caption' => $attachment->caption, 247 | 'description' => $attachment->description, 248 | 'hidden' => true, 249 | ]; 250 | 251 | try { 252 | return Attachment::create($create); 253 | } catch (Exception $e) { 254 | throw new MediaBrowserException($e->getMessage()); 255 | } 256 | } 257 | 258 | public function getFilePath(string $filename): string 259 | { 260 | $diskConfig = $this->getAndValidateDisk(config('mediable.disk')); 261 | 262 | $disk = $diskConfig['disk']; 263 | 264 | return Storage::disk($disk)->path($filename); 265 | } 266 | 267 | public function fileObject(string $path, bool $checkPath = true): File 268 | { 269 | try { 270 | $file = new File($path, $checkPath); 271 | } catch (Exception $e) { 272 | throw new MediaBrowserException($e->getMessage()); 273 | } 274 | 275 | return $file; 276 | } 277 | 278 | public function randomizeName(string $source): string 279 | { 280 | $extension = pathinfo($source, PATHINFO_EXTENSION); 281 | 282 | $destinationFilename = Str::random(12).'.'.$extension; 283 | 284 | $directory = pathinfo($source, PATHINFO_DIRNAME); 285 | 286 | return $directory.DIRECTORY_SEPARATOR.$destinationFilename; 287 | } 288 | 289 | public function prepareFileName(string $fileName): string 290 | { 291 | $extension = pathinfo($fileName, PATHINFO_EXTENSION); 292 | $name = pathinfo($fileName, PATHINFO_FILENAME); 293 | 294 | $slug = Str::slug($name); 295 | 296 | return $slug.'-'.time().'.'.$extension; 297 | } 298 | 299 | public function search(string $searchTerm, array $searchColumns): void 300 | { 301 | if ($searchTerm) { 302 | 303 | foreach ($searchColumns as $index => $column) { 304 | if ($index === 0) { 305 | $this->query->where($column, 'like', "%{$searchTerm}%"); 306 | } else { 307 | $this->query->orWhere($column, 'like', "%{$searchTerm}%"); 308 | } 309 | } 310 | } 311 | } 312 | 313 | public function uniqueMimes(): array 314 | { 315 | return Attachment::query()->distinct()->pluck('file_type')->sort()->values()->toArray(); 316 | } 317 | 318 | public function paginate(int $perPage): LengthAwarePaginator 319 | { 320 | return $this->query->paginate($perPage); 321 | } 322 | 323 | public function getAndValidateDisk(string $name): array 324 | { 325 | $disks = config('filesystems.disks'); 326 | 327 | if (! array_key_exists($name, $disks)) { 328 | throw new MediaBrowserException('Storage disk not found.'); 329 | } 330 | 331 | return ['disk' => $name, 'driver' => $disks[$name]]; 332 | } 333 | 334 | public function getMimeTypeStats() 335 | { 336 | return Attachment::select('file_type', DB::raw('count(*) as total'), DB::raw('sum(file_size) as total_size')) 337 | ->groupBy('file_type') 338 | ->get(); 339 | } 340 | 341 | public function getMimeTypeTotals() 342 | { 343 | return Attachment::select(DB::raw('count(*) as total'), DB::raw('sum(file_size) as total_size'))->first(); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/Components/MediaBrowser.php: -------------------------------------------------------------------------------- 1 | 'ID', 'file_name' => 'Name', 'file_type' => 'Type', 'file_size' => 'Size', 'file_dir' => 'Directory', 'file_url' => 'URL', 'title' => 'Title', 'caption' => 'Caption', 'description' => 'Description', 'sort_order' => 'Sort Order', 'created_at' => 'Created At', 'updated_at' => 'Updated At']; 59 | 60 | public int $perPage = 25; 61 | 62 | public array $perPageValues = [10, 25, 50, 75, 100, 0]; 63 | 64 | public string $orderBy = 'id'; 65 | 66 | public string $orderDir = 'DESC'; 67 | 68 | public array $orderDirValues = ['ASC' => 'Ascending', 'DESC' => 'Descending']; 69 | 70 | public ?int $audioElementId = null; 71 | 72 | public ?int $maxUploadSize = null; 73 | 74 | public ?int $maxFileUploads = null; 75 | 76 | public ?int $maxUploadFileSize = null; 77 | 78 | public ?int $postMaxSize = null; 79 | 80 | public ?int $memoryLimit = null; 81 | 82 | public int $imageHeight = 0; 83 | 84 | public int $imageWidth = 0; 85 | 86 | public int $imageType = 0; 87 | 88 | public function mount(?string $theme = null) 89 | { 90 | $this->generateUniqueId(); 91 | 92 | $this->modal = new ModalState; 93 | 94 | $this->alert = new AlertState; 95 | 96 | $this->attachment = new AttachmentState; 97 | 98 | $this->panel = new PanelState(thumbMode: true); 99 | 100 | $this->show = new ShowState; 101 | 102 | $this->theme = $theme ?? config('mediable.theme'); 103 | 104 | $this->maxUploadSize = $this->getMaxUploadSize(); 105 | $this->maxFileUploads = $this->getMaxFileUploads(); 106 | $this->maxUploadFileSize = $this->getMaxUploadFileSize(); 107 | $this->postMaxSize = $this->getPostMaxSize(); 108 | $this->memoryLimit = $this->getMemoryLimit(); 109 | 110 | $this->resetModal(); 111 | 112 | $this->hasExtension('gd'); 113 | 114 | Eloquent::garbage(); 115 | 116 | if ($this->hasStoreAttachmentId()) { 117 | $id = $this->getStoreAttachmentId(); 118 | $this->toggleAttachment($id); 119 | } 120 | } 121 | 122 | public function boot() 123 | { 124 | $this->dispatch( 125 | 'server:limits', 126 | maxUploadSize: $this->maxUploadSize, 127 | maxFileUploads: $this->maxFileUploads, 128 | maxUploadFileSize: $this->maxUploadFileSize, 129 | postMaxSize: $this->postMaxSize, 130 | memoryLimit: $this->memoryLimit 131 | ); 132 | 133 | $this->uniqueMimeTypes = Eloquent::uniqueMimes(); 134 | } 135 | 136 | #[On('mediable.open')] 137 | public function open(?string $id = null): void 138 | { 139 | $this->modal = new ModalState(true, $id ?? ''); 140 | } 141 | 142 | #[On('mediable.close')] 143 | public function close(): void 144 | { 145 | $this->closeModal(); 146 | } 147 | 148 | #[On('audio.start')] 149 | public function playAudio($id): void 150 | { 151 | $this->audioElementId = $id; 152 | } 153 | 154 | #[On('audio.pause')] 155 | public function pauseAudio($id): void 156 | { 157 | if ($this->audioElementId == $id) { 158 | $this->audioElementId = null; 159 | } 160 | } 161 | 162 | #[On('media.alert')] 163 | public function alert($event): void 164 | { 165 | $this->alert = new AlertState( 166 | show: true, 167 | type: $event['type'], 168 | message: $event['message'] 169 | ); 170 | } 171 | 172 | public function enableThumbMode(): self 173 | { 174 | $this->panel = new PanelState(thumbMode: true); 175 | 176 | return $this; 177 | } 178 | 179 | public function enablePreviewMode(): self 180 | { 181 | $this->panel = new PanelState(previewMode: true); 182 | 183 | return $this; 184 | } 185 | 186 | public function enableEditorMode(): self 187 | { 188 | $this->panel = new PanelState(editorMode: true); 189 | 190 | $this->prepareImageEditor(); 191 | 192 | return $this; 193 | } 194 | 195 | public function enableUploadMode(): self 196 | { 197 | $this->panel = new PanelState(uploadMode: true); 198 | 199 | return $this; 200 | } 201 | 202 | public function toggleSidebar(): self 203 | { 204 | $this->show = new ShowState(showSidebar: ! $this->show->isShowSidebar(), showMetaInfo: $this->show->isShowMetaInfo()); 205 | 206 | return $this; 207 | } 208 | 209 | public function toggleMetaInfo(): self 210 | { 211 | $this->show = new ShowState(showMetaInfo: ! $this->show->isShowMetaInfo(), showSidebar: $this->show->isShowSidebar()); 212 | 213 | return $this; 214 | } 215 | 216 | public function createAttachments(): void 217 | { 218 | Eloquent::create($this->files); 219 | 220 | $count = count($this->files); 221 | $message = (count($this->files) > 1) ? "Created $count attachment(s) successfully!" : 'Created attachment successfully!'; 222 | 223 | $this->alert = new AlertState( 224 | show: true, 225 | type: 'success', 226 | message: $message 227 | ); 228 | 229 | $this->fill([ 230 | 'files' => [], 231 | 'orderBy' => 'id', 232 | 'orderDir' => 'DESC', 233 | ]); 234 | 235 | $this->enableThumbMode(); 236 | } 237 | 238 | public function updateAttachment(): void 239 | { 240 | $data = [ 241 | 'title' => $this->attachment->title, 242 | 'caption' => $this->attachment->caption, 243 | 'description' => $this->attachment->description, 244 | 'sort_order' => $this->attachment->sort_order, 245 | 'styles' => $this->attachment->styles, 246 | ]; 247 | 248 | $rules = [ 249 | 'title' => 'required|string|max:255', 250 | 'caption' => 'nullable|string|max:255', 251 | 'description' => 'nullable|string|max:1000', 252 | 'sort_order' => 'required|integer', 253 | 'styles' => 'nullable|string|max:500', 254 | ]; 255 | 256 | $validator = Validator::make($data, $rules); 257 | 258 | if ($validator->fails()) { 259 | $this->alert = new AlertState( 260 | show: true, 261 | type: 'error', 262 | message: $validator->errors()->first() 263 | ); 264 | } else { 265 | $validated = $validator->validated(); 266 | 267 | Eloquent::update($this->attachment->id, $validated); 268 | 269 | $this->alert = new AlertState( 270 | show: true, 271 | type: 'success', 272 | message: 'Attachment updated successfully!' 273 | ); 274 | } 275 | } 276 | 277 | public function toggleAttachment(int $id): void 278 | { 279 | $item = Attachment::find($id); 280 | 281 | if (! $item) { 282 | return; 283 | } 284 | 285 | $found = in_array($item['id'], array_column($this->selected, 'id')); 286 | 287 | if ($found) { 288 | foreach ($this->selected as $key => $value) { 289 | if ($value['id'] === $item['id']) { 290 | unset($this->selected[$key]); 291 | break; 292 | } 293 | } 294 | } else { 295 | array_push($this->selected, $item); 296 | } 297 | 298 | $this->attachment = AttachmentState::fromAttachment($item); 299 | 300 | $this->applyImageInfo($item); 301 | 302 | $this->dispatch('mediable.scroll', id: $this->attachment->id); 303 | 304 | $this->alert = new AlertState; 305 | 306 | $this->storeAttachmentId($this->attachment->id); 307 | } 308 | 309 | public function setActiveAttachment(Attachment $item): void 310 | { 311 | $found = in_array($item['id'], array_column($this->selected, 'id')); 312 | 313 | if (! $found) { 314 | return; 315 | } 316 | 317 | $this->attachment = AttachmentState::fromAttachment($item); 318 | 319 | $this->applyImageInfo($item); 320 | 321 | $this->enablePreviewMode(); 322 | 323 | $this->alert = new AlertState; 324 | 325 | $this->storeAttachmentId($this->attachment->id); 326 | } 327 | 328 | public function clearSelected(): void 329 | { 330 | $this->selected = []; 331 | 332 | $this->attachment = new AttachmentState; 333 | 334 | $this->resetPage(); 335 | 336 | $this->enableThumbMode(); 337 | } 338 | 339 | public function confirmDelete() 340 | { 341 | $this->dispatch('mediable.confirm', type: 'delete.selected', message: 'Are you sure you want to delete selected attachments?'); 342 | } 343 | 344 | public function deleteAttachment(int $id): void 345 | { 346 | Eloquent::delete($id); 347 | 348 | $this->selected = array_filter($this->selected, function ($item) use ($id) { 349 | return $item['id'] !== $id; 350 | }); 351 | 352 | if (count($this->selected)) { 353 | $end = end($this->selected); 354 | if ($end->count()) { 355 | $this->attachment = AttachmentState::fromAttachment($end); 356 | $this->storeAttachmentId($this->attachment->id); 357 | } 358 | } else { 359 | $this->deleteStoreAttachmentId(); 360 | $this->attachment = new AttachmentState; 361 | } 362 | 363 | $this->alert = new AlertState( 364 | show: true, 365 | type: 'success', 366 | message: 'Attachment deleted successfully!' 367 | ); 368 | } 369 | 370 | #[On('delete.selected')] 371 | public function deleteSelected(): void 372 | { 373 | foreach ($this->selected as $item) { 374 | Eloquent::delete($item['id']); 375 | } 376 | 377 | $count = count($this->selected); 378 | 379 | $message = ($count > 1) ? "Deleted $count attachment(s) successfully!" : 'Deleted attachment successfully!'; 380 | 381 | $this->alert = new AlertState( 382 | show: true, 383 | type: 'success', 384 | message: $message 385 | ); 386 | 387 | $this->attachment = new AttachmentState; 388 | 389 | $this->clearSelected(); 390 | } 391 | 392 | public function resetModal(): void 393 | { 394 | $this->clearFiles(); 395 | $this->clearSelected(); 396 | $this->enableThumbMode(); 397 | $this->resetPage(); 398 | } 399 | 400 | public function insertMedia(): void 401 | { 402 | if ($this->modal->hasElementId()) { 403 | $this->dispatch(BrowserEvents::INSERT->value, selected: $this->selected); 404 | } else { 405 | $this->dispatch(BrowserEvents::DEFAULT->value, $this->selected); 406 | } 407 | 408 | $this->closeModal(); 409 | } 410 | 411 | public function closeModal(): void 412 | { 413 | $this->modal = new ModalState( 414 | show: false, 415 | elementId: '' 416 | ); 417 | 418 | $this->resetModal(); 419 | } 420 | 421 | public function expandModal(): void 422 | { 423 | $this->fullScreen = ! $this->fullScreen; 424 | } 425 | 426 | public function closeAlert(): void 427 | { 428 | $this->alert = new AlertState; 429 | } 430 | 431 | public function updatedFiles(): void 432 | { 433 | $this->validate(config('mediable.validation')); 434 | } 435 | 436 | public function updatedSelectedMimeType(): void 437 | { 438 | $this->resetPage(); 439 | } 440 | 441 | public function clearFile(int $index): void 442 | { 443 | try { 444 | array_splice($this->files, $index, 1); 445 | } catch (MediaBrowserException $e) { 446 | throw new MediaBrowserException($e->getMessage()); 447 | } 448 | } 449 | 450 | public function clearFiles(): void 451 | { 452 | $this->files = []; 453 | } 454 | 455 | public function toggleOrderDir() 456 | { 457 | $this->orderDir = $this->orderDir === 'asc' ? 'desc' : 'asc'; 458 | } 459 | 460 | public function getTotalUploadSize() 461 | { 462 | return array_reduce($this->files, function ($carry, $file) { 463 | return $carry + $file->getSize(); 464 | }, 0); 465 | } 466 | 467 | public function resetAudioElement() 468 | { 469 | if ($this->audioElementId) { 470 | $this->audioElementId = null; 471 | } 472 | } 473 | 474 | public function updatingPage(): void 475 | { 476 | $this->resetAudioElement(); 477 | } 478 | 479 | public function applyImageInfo($item): void 480 | { 481 | if ($this->mimeTypeImage($item['file_type'])) { 482 | [$width, $height, $type] = $this->getImageSize(Eloquent::getFilePath($item['file_dir'])); 483 | 484 | if ($type) { 485 | $this->fill([ 486 | 'imageWidth' => $width, 487 | 'imageHeight' => $height, 488 | 'scaleWidth' => $width, 489 | 'scaleHeight' => $height, 490 | 'scaleMode' => null, 491 | ]); 492 | } 493 | } 494 | } 495 | 496 | public function prepareImageEditor(): void 497 | { 498 | if (! $this->panel->IsEditorMode()) { 499 | return; 500 | } 501 | 502 | $this->primaryId = $this->attachment->getId(); 503 | 504 | $source = $this->attachment->getFileDir(); 505 | 506 | $destination = Eloquent::randomizeName($source); 507 | 508 | $destination = Eloquent::copyImageFromTo($source, $destination); 509 | 510 | $item = Eloquent::saveImageToDatabase($this->attachment, $destination); 511 | 512 | $this->attachment = AttachmentState::fromAttachment($item); 513 | 514 | $this->editHistory = []; 515 | 516 | $this->selectedForm = ''; 517 | } 518 | 519 | public function saveEditorChanges() 520 | { 521 | if (! $this->attachment->id) { 522 | return; 523 | } 524 | 525 | Eloquent::enable($this->attachment->id); 526 | 527 | $this->fillEditorProperties(); 528 | 529 | $this->resetPage(); 530 | 531 | $this->editHistory = []; 532 | 533 | $this->primaryId = null; 534 | 535 | $this->enableThumbMode(); 536 | } 537 | 538 | public function undoEditorChanges() 539 | { 540 | if (! $this->primaryId) { 541 | return; 542 | } 543 | 544 | $row = Eloquent::load($this->primaryId); 545 | 546 | $this->primaryId = $row->id; 547 | 548 | $source = $row->file_dir; 549 | 550 | $destination = Eloquent::randomizeName($source); 551 | 552 | $destination = Eloquent::copyImageFromTo($source, $destination); 553 | 554 | $item = Eloquent::saveImageToDatabase($this->attachment, $destination); 555 | 556 | $this->attachment = AttachmentState::fromAttachment($item); 557 | 558 | $this->editHistory = []; 559 | } 560 | 561 | public function closeImageEditor() 562 | { 563 | $this->fillEditorProperties(); 564 | 565 | $this->editHistory = []; 566 | 567 | $this->primaryId = null; 568 | 569 | $this->enableThumbMode(); 570 | } 571 | 572 | public function generateUniqueId() 573 | { 574 | $this->uniqueId = uniqid(); 575 | } 576 | 577 | private function renderView(LengthAwarePaginator $paginator) 578 | { 579 | return view('mediable::'.$this->theme.'.media-browser', [ 580 | 'uniqueId' => $this->uniqueId, 581 | 'data' => $paginator, 582 | ]); 583 | } 584 | 585 | public function render() 586 | { 587 | Eloquent::query($this->orderBy, $this->orderDir, $this->selectedMimeType); 588 | 589 | Eloquent::search($this->searchTerm, $this->searchColumns); 590 | 591 | $paginator = Eloquent::paginate($this->perPage); 592 | 593 | if ($paginator->isEmpty()) { 594 | $this->enableUploadMode(); 595 | } 596 | 597 | return $this->renderView($paginator); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /resources/views/tailwind/includes/form.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 | @if ($this->mimeTypeImage($this->attachment->file_type)) 15 |
16 | 17 |
18 | @endif 19 | 20 | @if($selectedForm == '') 21 | @foreach($this->availableForms as $key => $value) 22 | 30 | @endforeach 31 | @endif 32 | 33 | @if($selectedForm == 'image-flip') 34 |
35 | 36 | 42 |
43 |
44 | 49 | @if(count($editHistory)) 50 | 54 | 58 | @endif 59 | 63 |
64 | @endif 65 | 66 | @if($selectedForm == 'image-scale') 67 |
68 | 69 | 75 |
76 |
77 | 78 | 79 |
80 |
81 | 82 | 83 |
84 |
85 | 90 | @if(count($editHistory)) 91 | 95 | 99 | @endif 100 | 104 |
105 | @endif 106 | 107 | @if($selectedForm == 'image-filter') 108 |
109 | 110 | 116 |
117 | @if($filterMode == IMG_FILTER_CONTRAST) 118 |
119 | 120 | 121 |
122 | @endif 123 | @if($filterMode == IMG_FILTER_BRIGHTNESS) 124 |
125 | 126 | 127 |
128 | @endif 129 | @if($filterMode == IMG_FILTER_COLORIZE) 130 |
131 | 132 | 133 |
134 | @endif 135 | @if($filterMode == IMG_FILTER_SMOOTH) 136 |
137 | 138 | 139 |
140 | @endif 141 | @if($filterMode == IMG_FILTER_PIXELATE) 142 |
143 | 144 | 145 |
146 | @endif 147 |
148 | 153 | @if(count($editHistory)) 154 | 158 | 162 | @endif 163 | 167 |
168 | @endif 169 | 170 | @if($selectedForm == 'image-rotate') 171 |
172 | 173 | 174 |
175 |
176 | 177 | 178 |
179 |
180 | 181 | 182 |
183 |
184 | 189 | @if(count($editHistory)) 190 | 194 | 198 | @endif 199 | 203 |
204 | @endif 205 | 206 | @if($selectedForm == 'image-crop') 207 |
208 | 209 | 210 |
211 |
212 | 213 | 214 |
215 |
216 | 217 | 218 |
219 |
220 | 221 | 222 |
223 |
224 | 229 | @if(count($editHistory)) 230 | 234 | 238 | @endif 239 | 243 |
244 | @endif 245 | 246 | @if($selectedForm == 'image-text') 247 |
248 | 249 | 250 |
251 |
252 | 253 | 259 |
260 |
261 | 262 | 263 |
264 |
265 | 266 | 267 |
268 |
269 | 270 | 271 |
272 |
273 | 278 | @if(count($editHistory)) 279 | 283 | 287 | @endif 288 | 292 |
293 | @endif 294 | 295 |
296 |
297 |
298 | 299 |
300 |
301 |
302 |
--------------------------------------------------------------------------------