├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── filemanager.php ├── doc └── img.png ├── pint.json ├── resources ├── dist │ ├── css │ │ └── filemanager.css │ └── js │ │ ├── Components │ │ └── Filemanager │ │ │ ├── Actions │ │ │ ├── FileAction.js │ │ │ └── FolderAction.js │ │ │ ├── DragableFiles.js │ │ │ ├── File.js │ │ │ ├── Filemanager.js │ │ │ ├── Folder.js │ │ │ ├── Icons │ │ │ └── Icons.js │ │ │ ├── InputEditFolder.js │ │ │ ├── Support │ │ │ ├── ArraySupport.js │ │ │ ├── FileSize.js │ │ │ ├── FolderSupport.js │ │ │ ├── ImageSupport.js │ │ │ └── Notification.js │ │ │ ├── Thumbnail │ │ │ └── Thumbnail.js │ │ │ └── functions │ │ │ └── Ui.js │ │ └── filemanager.js └── views │ ├── .gitkeep │ └── components │ ├── filemanager │ ├── files-up-loader.blade.php │ ├── folder.blade.php │ ├── folders.blade.php │ └── thumbnail.blade.php │ ├── icons │ ├── folder.blade.php │ └── plus.blade.php │ └── livewire │ └── file-manager │ └── uploader.blade.php └── src ├── Commands └── FileManagerCommand.php ├── Facades └── FileManager.php ├── FileManager.php ├── FileManagerServiceProvider.php └── Livewire └── FileManager ├── Actions ├── CreateFolderAction.php ├── DeleteFile.php ├── DeleteFolder.php ├── FetchFiles.php ├── FetchFolders.php ├── HasResponseMessageAction.php └── SaveFiles.php ├── DTOs ├── FileDTO.php └── FolderDTO.php └── Uploader.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `FileManager` will be documented in this file. 4 | 5 | ## Pint & phpStan Pass - 2023-03-22 6 | 7 | Pint & phpStan Pass 8 | 9 | ## Final config for Assets js & css - 2023-03-21 10 | 11 | Add final config for Assets js & css installation 12 | 13 | ## fix assets action create a folder - 2023-03-21 14 | 15 | fix assets action create a folder 16 | 17 | ## fix js app asset - 2023-03-21 18 | 19 | Adding Alpine ro filemanager.js in resources/dist 20 | 21 | ## fix assets - 2023-03-21 22 | 23 | fixing js assets due to generate bad array json 24 | 25 | ## Add assets - 2023-03-21 26 | 27 | Adding assets js and css assets 28 | Fix on creating a folder 29 | 30 | ## Change readme file - 2023-03-21 31 | 32 | Changing the readme file 33 | 34 | ## Add unit test - 2023-03-21 35 | 36 | Adding unit testion for the component 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | If you want to contribute, contact [me at contact@webplusm.net](contact@webplusm.net) 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) webplusmultimedia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manage files with this file manager made with alpinejs and livewire and tailwindcss 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/webplusmultimedia/filemanager.svg?style=flat-square)](https://packagist.org/packages/webplusmultimedia/filemanager) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/webplusmultimedia/filemanager/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/webplusmultimedia/filemanager/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/webplusmultimedia/filemanager/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/webplusmultimedia/filemanager/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/webplusmultimedia/filemanager.svg?style=flat-square)](https://packagist.org/packages/webplusmultimedia/filemanager) 7 | 8 | Want to manage your files and directories in a file manager? 9 | 10 | This one is for you. Here is the file manager made with alpineJs, Livewire and Tailwind CSS for Laravel. 11 | 12 | This is a simple one, just adding files and directories to a root directory(default is medias) in your public storage path. You can change it in the filemanager config file. 13 | 14 | [![img.png](https://i.postimg.cc/XvQ1M2gt/img.png)](https://postimg.cc/v1xtftkv) 15 | 16 | ## Installation 17 | 18 | You can install the package via composer: 19 | 20 | ```bash 21 | composer require webplusmultimedia/filemanager 22 | ``` 23 | 24 | You can publish the config file and change the root directory if you want: 25 | 26 | ```bash 27 | php artisan vendor:publish --tag="filemanager-config" 28 | ``` 29 | 30 | This is the contents of the published config file: 31 | 32 | ```php 33 | return [ 34 | 'root' => 'medias', 35 | ]; 36 | ``` 37 | 38 | Optionally, you can publish the views, but not recommended because will failing at an future update. 39 | 40 | ```bash 41 | php artisan vendor:publish --tag="filemanager-views" 42 | ``` 43 | Finaly, you need to compile your assets with FileManager ones like that : 44 | ```css 45 | /* before @tailwind base in your resources/css/app.css */ 46 | @import "./vendor/webplusmultimedia/filemanager/resources/dist/css/filemanager.css"; 47 | @tailwind base; 48 | ``` 49 | ```javascript 50 | /* in your resources/js/app.js */ 51 | import './vendor/webplusmultimedia/filemanager/resources/dist/js/filemanager' 52 | ``` 53 | ```javascript 54 | /* put that line in content key on your tailwind.config.js */ 55 | content:[ 56 | '*** Others paths ***', 57 | './vendor/webplusmultimedia/filemanager/resources/dist/js/Components/**/*.js' 58 | ] 59 | ``` 60 | ## Usage 61 | For simple use in a blade view : 62 | ```html 63 |
64 |
65 |
66 |
67 |

{{ __("Téléversement de fichiers") }}

68 | 69 | 70 |
71 |
72 |
73 |
74 | ``` 75 | 76 | ## Testing 77 | 78 | ```bash 79 | composer test 80 | ``` 81 | 82 | ## Changelog 83 | 84 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 85 | 86 | ## Contributing 87 | 88 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 89 | 90 | ## Security Vulnerabilities 91 | 92 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 93 | 94 | ## Credits 95 | 96 | - [Webplusm Multimedia](https://github.com/webplusmultimedia) 97 | 98 | ## License 99 | 100 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 101 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webplusmultimedia/filemanager", 3 | "description": "Manage files with this file manager made with alpinejs, livewire and tailwind CSS", 4 | "keywords": [ 5 | "webplusmultimedia", 6 | "laravel", 7 | "filemanager" 8 | ], 9 | "homepage": "https://github.com/webplusmultimedia/filemanager", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "webplusm", 14 | "email": "contact@webplusm.net", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "illuminate/contracts": "^10.0", 21 | "livewire/livewire": "^2.12", 22 | "spatie/laravel-package-tools": "^1.14.0" 23 | }, 24 | "require-dev": { 25 | "laravel/pint": "^1.0", 26 | "nunomaduro/collision": "^7.9", 27 | "nunomaduro/larastan": "^2.0.1", 28 | "orchestra/testbench": "^8.0", 29 | "pestphp/pest": "^2.0", 30 | "pestphp/pest-plugin-arch": "^2.0", 31 | "pestphp/pest-plugin-faker": "^2.0", 32 | "pestphp/pest-plugin-laravel": "^2.0", 33 | "pestphp/pest-plugin-livewire": "^2.0", 34 | "phpstan/extension-installer": "^1.1", 35 | "phpstan/phpstan-deprecation-rules": "^1.0", 36 | "phpstan/phpstan-phpunit": "^1.0" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Webplusmultimedia\\FileManager\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Webplusmultimedia\\FileManager\\Tests\\": "tests/" 46 | } 47 | }, 48 | "scripts": { 49 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 50 | "analyse": "vendor/bin/phpstan analyse", 51 | "test": "vendor/bin/pest", 52 | "test-coverage": "vendor/bin/pest --coverage", 53 | "format": "vendor/bin/pint" 54 | }, 55 | "config": { 56 | "sort-packages": true, 57 | "allow-plugins": { 58 | "pestphp/pest-plugin": true, 59 | "phpstan/extension-installer": true 60 | } 61 | }, 62 | "extra": { 63 | "laravel": { 64 | "providers": [ 65 | "Webplusmultimedia\\FileManager\\FileManagerServiceProvider" 66 | ], 67 | "aliases": { 68 | "FileManager": "Webplusmultimedia\\FileManager\\Facades\\FileManager" 69 | } 70 | } 71 | }, 72 | "minimum-stability": "dev", 73 | "prefer-stable": true 74 | } 75 | -------------------------------------------------------------------------------- /config/filemanager.php: -------------------------------------------------------------------------------- 1 | 'medias', 5 | ]; 6 | -------------------------------------------------------------------------------- /doc/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webplusmultimedia/FileManager/d881e4d8fe2927524fc74d958a61838bd10d9038/doc/img.png -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "exclude": [ 4 | "build" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /resources/dist/css/filemanager.css: -------------------------------------------------------------------------------- 1 | @layer components{ 2 | .left__side{ 3 | @apply md:w-96 4 | } 5 | .right__side{ 6 | @apply w-full 7 | } 8 | .folder{ 9 | @apply flex gap-2 text-gray-500 justify-between px-3 py-1 rounded-md items-center 10 | } 11 | .folder:hover{ 12 | @apply bg-blue-50 13 | } 14 | .folder.active{ 15 | @apply bg-blue-50 text-blue-600 16 | } 17 | .folder.active .folder-icon svg{ 18 | @apply fill-blue-500 19 | } 20 | .folder svg.icon-plus { 21 | @apply fill-gray-300 22 | } 23 | .folder:hover >.icon-plus { 24 | @apply fill-blue-500 25 | } 26 | .folder.active svg.icon-plus { 27 | @apply fill-blue-500 28 | } 29 | 30 | .folder .folder-icon{ 31 | @apply flex gap-2 items-center grow 32 | } 33 | [x-cloak]{ 34 | @apply !hidden 35 | } 36 | .input-component { 37 | @apply border-gray-200 invalid:border-red-500 invalid:text-red-600 focus:invalid:border-red-500 focus:invalid:ring-red-500; 38 | } 39 | .wrapper-webnotification{ 40 | @apply absolute top-2 right-6 max-w-[30rem] min-w-[25rem] flex flex-col gap-3 41 | 42 | } 43 | .webnotification{ 44 | @apply relative flex gap-5 items-center p-3 rounded shadow 45 | before:content-[''] before:absolute before:bottom-0 before:left-0 before:right-0 before:bottom-0 before:h-1 46 | } 47 | 48 | .webnotification.success{ 49 | @apply before:bg-green-600 text-green-600 bg-green-100 50 | } 51 | 52 | .webnotification.info{ 53 | @apply text-cyan-600 bg-cyan-100 before:bg-cyan-600 54 | } 55 | .webnotification.warning{ 56 | @apply text-amber-600 bg-amber-100 before:bg-amber-600 57 | } 58 | .webnotification.danger{ 59 | @apply text-pink-600 bg-pink-100 before:bg-pink-600 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Actions/FileAction.js: -------------------------------------------------------------------------------- 1 | import {Danger, Success, Warning} from "./../Support/Notification"; 2 | 3 | /** 4 | * 5 | * @param {File[]} files 6 | * @param {Proxy} data 7 | */ 8 | export function SaveFilesAction(files, data) { 9 | if (!files.length) { 10 | Warning('Aucun fichier à transféré'); 11 | return 12 | } 13 | data.resetProgress() 14 | data.$wire.uploadMultiple('photos', files, (uploadFilenames) => { 15 | data.resetProgress() 16 | if (uploadFilenames) { 17 | let folder = data.selectedFolder ? data.selectedFolder.id : null 18 | data.$wire.saveFiles(folder).then((value) => { 19 | let result = value?.result 20 | if (value && !value.error && result.files && result.files.length) { 21 | data.files.unshift(...result.files) 22 | Success(value.message) 23 | return 24 | } 25 | Danger(value?.message ?? "Erreur sur les fichiers téléchargés") 26 | }) 27 | } 28 | 29 | }, 30 | () => { 31 | data.resetProgress() 32 | Danger("Erreur sur les fichiers téléchargés") 33 | }, 34 | (e) => { 35 | data.progress = 100 - Math.ceil((e.loaded / e.total) * 100) 36 | }) 37 | } 38 | /** 39 | * 40 | * @param {Proxy} data 41 | * @return void 42 | */ 43 | export async function DeleteFile(data) { 44 | const root = data.selectedFolder ? data.selectedFolder.id + '/' + data.file.filename : data.file.filename, 45 | result = await data.$wire.deleteFile(root) 46 | 47 | if (!result.error) { 48 | Success(result.message) 49 | data.showImg = false 50 | data.files.splice(data.index, 1); 51 | return 52 | } 53 | Warning(result.message) 54 | } 55 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Actions/FolderAction.js: -------------------------------------------------------------------------------- 1 | import {Success, Warning} from "./../Support/Notification"; 2 | /** 3 | * 4 | * @param {Proxy} data 5 | * @return void 6 | */ 7 | export async function SaveFolderAction(data) { 8 | const name = data.$refs.folderEdit.value 9 | if (!name) { 10 | data.removeInput() 11 | return 12 | } 13 | const result = await data.$wire.createFolder(name, data.folder.parent) 14 | if(result.error){ 15 | Warning(result.message) 16 | return 17 | } 18 | data.folder.name = name 19 | data.folder.children = [] 20 | data.folder.files = [] 21 | data.folder.id = result.result.id 22 | Success(`Le répertoire ${name} a été ajouté`) 23 | data.isNewFolder = false 24 | data.files =[] 25 | data.setFiles() 26 | data.folderEdit = null 27 | } 28 | /** 29 | * 30 | * @param {Proxy} data 31 | * @return void 32 | */ 33 | export async function DeleteFolderAction(data) { 34 | const result = await data.$wire.deleteFolder(data.selectedFolder.id) 35 | if (!result.error) { 36 | await data.supportArray.deleteFolder(data.selectedFolder.parent, data.selectedFolder.id) 37 | Success(result.message) 38 | data.selectedFolder = null 39 | data.setFiles() 40 | return 41 | } 42 | Warning(result.message) 43 | } 44 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/DragableFiles.js: -------------------------------------------------------------------------------- 1 | import {ImageSupport} from "./../Filemanager/Support/ImageSupport"; 2 | import {SaveFilesAction} from "./../Filemanager/Actions/FileAction"; 3 | import {DeleteFolderAction} from "../Filemanager/Actions/FolderAction"; 4 | 5 | export function dragableFiles() { 6 | return { 7 | progress: 100, 8 | resetProgress() { 9 | this.progress = 100 10 | }, 11 | 'overZone': { 12 | ['@dragover.prevent.stop']() { 13 | 14 | if (this.$event.target.classList.contains('dropZone')) { 15 | 16 | } 17 | }, 18 | ['@dragenter.prevent.stop']() { 19 | this.$refs.dropzone.classList.remove('pointer-events-none') 20 | this.$refs.dropzone.classList.add('after:opacity-75', 'opacity-100', 'pointer-events-auto') 21 | }, 22 | ['@dragleave.prevent.stop']() { 23 | if (this.$event.target === this.$refs.dropzone) { 24 | this.$refs.dropzone.classList.remove('after:opacity-75', 'opacity-100', 'pointer-events-auto') 25 | this.$refs.dropzone.classList.add('pointer-events-none') 26 | } 27 | return false 28 | }, 29 | }, 30 | 'dropZone': { 31 | ['@drop.prevent.stop']() { 32 | if (this.$event.target.classList.contains('dropZone')) { 33 | this.$refs.dropzone.classList.remove('after:opacity-75', 'opacity-100', 'pointer-events-auto') 34 | this.$refs.dropzone.classList.add('pointer-events-none') 35 | 36 | SaveFilesAction(ImageSupport().getDesireFiles(this.$event.dataTransfer.files), this.$data) 37 | } 38 | } 39 | }, 40 | 'deleteFolder': { 41 | async ['@click.stop']() { 42 | await DeleteFolderAction(this.$data) 43 | }, 44 | ['x-show']() { 45 | return this.selectedFolder && this.selectedFolder.children && !this.selectedFolder.children.length 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/File.js: -------------------------------------------------------------------------------- 1 | export function WbFile() { 2 | return { 3 | get title(){ 4 | let dir = (this.selectedFolder?this.selectedFolder.id: '/')??'-- Nouveau répertoire --' 5 | return `.:: ${dir}` 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Filemanager.js: -------------------------------------------------------------------------------- 1 | import {Icons} from "./Icons/Icons"; 2 | import {arraySupport} from "./Support/ArraySupport"; 3 | import {ui} from "./functions/Ui"; 4 | 5 | export function Filemanager(folders) { 6 | return { 7 | folders: {children: [], files: []}, 8 | files: [], 9 | getFiles() { 10 | if (!this.selectedFolder) { 11 | return this.folders.files 12 | } 13 | if (this.selectedFolder.files && this.selectedFolder.files.length) { 14 | return this.selectedFolder.files; 15 | } 16 | return [] 17 | }, 18 | setFiles() { 19 | if (!this.selectedFolder) { 20 | this.files = this.folders.files 21 | return 22 | } 23 | if (this.selectedFolder.files && this.selectedFolder.files.length) { 24 | this.files = this.selectedFolder.files; 25 | return 26 | } 27 | this.files = [] 28 | }, 29 | showFiles() { 30 | return this.files.length > 0 31 | }, 32 | showZoneFiles(){ 33 | return !(this.selectedFolder && !this.selectedFolder.id); 34 | }, 35 | isNewFolder: false, 36 | folderEdit: null, 37 | supportArray: arraySupport(), 38 | selectedFolder: null, 39 | getTitle(folder) { 40 | return folder ? folder.name : '/' 41 | }, 42 | getInput() { 43 | return `` 44 | }, 45 | keyId() { 46 | return Math.random().toString(36).slice(2) 47 | }, 48 | async renderFolder(folder) { 49 | let isBtnAddingNewFolder = !folder || (folder && folder.id !== null) 50 | let htmlTemplate = 51 | `
52 |
53 |
54 | ${(folder && !folder.open) ? Icons().folder : Icons().folderOpen} 55 | ${isBtnAddingNewFolder ? this.getTitle(folder) : this.getInput()} 56 |
57 | ${ui(isBtnAddingNewFolder)} 58 |
59 |
` 60 | if (folder && folder.children && folder.children.length) { 61 | htmlTemplate += `` 66 | } 67 | return htmlTemplate; 68 | }, 69 | async init() { 70 | this.folders.children = folders 71 | this.supportArray.items = folders 72 | this.folders.files = await this.$wire.getFiles() 73 | this.files = this.folders.files 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Folder.js: -------------------------------------------------------------------------------- 1 | import {Info, Warning} from "./Support/Notification"; 2 | 3 | /** 4 | * 5 | * @param folder 6 | * @return {{addNewFolder: {"@click.prevent.stop"(): Promise}, fakeFolder(): {parent: null, name: string, id: null, open: boolean}, active: boolean, _newFolder: null, getActive(): boolean, loadChildren: {"@click.prevent.stop"(): Promise}, getFolders(): Promise, checkOpen(): void, parentId: null}|{parent: *, name: string, id: null, open: boolean}|boolean} 7 | */ 8 | export function folder(folder) { 9 | 10 | 11 | return { 12 | parentId: null, 13 | active: false, 14 | async getFolders() { 15 | if (folder && folder.id && (!folder.children || !folder.files)) { 16 | folder.children = await this.$wire.getFolders(folder.id) 17 | folder.files = await this.$wire.getFiles(folder.id) 18 | } 19 | this.setFiles() 20 | }, 21 | getActive() { 22 | return this.selectedFolder === folder 23 | }, 24 | _newFolder: null, 25 | fakeFolder() { 26 | let parent = folder ? folder.id : null 27 | return {name: null, id: null, parent: parent, open: true, children: []} 28 | }, 29 | 30 | 'addNewFolder': { 31 | async ['@click.prevent.stop']() { 32 | await this.getFolders() 33 | if (this.isNewFolder && this.folderEdit) { 34 | Warning("Vous ajouter déjà un répertoire") 35 | return; 36 | } 37 | if (this.isNewFolder && !this.folderEdit) { 38 | Info("Suppression edit") 39 | this.supportArray.deleteItemEditFolder() 40 | } 41 | 42 | this.isNewFolder = true 43 | this.supportArray.editFolder = this.fakeFolder() 44 | this.supportArray.parentEditFolder = folder 45 | this.selectedFolder = this.supportArray.editFolder 46 | if (folder) { 47 | folder.children.unshift(this.supportArray.editFolder) 48 | } else { 49 | this.folders.children.unshift(this.supportArray.editFolder) 50 | } 51 | } 52 | }, 53 | 54 | 'loadChildren': { 55 | async ['@click.prevent.stop']() { 56 | this.checkOpen() 57 | this.selectedFolder = folder 58 | await this.getFolders(); 59 | } 60 | }, 61 | checkOpen() { 62 | 63 | if (folder && folder.id && this.selectedFolder === folder) { 64 | folder.children.forEach((v) => { 65 | if (v.id) { 66 | v.open = !v.open 67 | } 68 | }) 69 | } else if (folder && folder.children) { 70 | folder.children.forEach((v) => { 71 | v.open = true 72 | }) 73 | } 74 | } 75 | 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Icons/Icons.js: -------------------------------------------------------------------------------- 1 | export const Icons = () => { 2 | return { 3 | folder: ` 4 | `, 5 | folderOpen: ` 6 | 7 | `, 8 | plus: ` 9 | `, 10 | enter: ` 11 | `, 12 | 13 | info: ` 14 | 15 | `, 16 | warning: ` 17 | 18 | `, 19 | success: ` 20 | 21 | `, 22 | danger: ` 23 | 24 | 25 | ` 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/InputEditFolder.js: -------------------------------------------------------------------------------- 1 | import {Success, Warning} from "./Support/Notification"; 2 | import {SaveFolderAction} from "./Actions/FolderAction"; 3 | 4 | export function inputEditFolder() { 5 | return { 6 | removeInput() { 7 | this.supportArray.deleteItemEditFolder() 8 | this.isNewFolder = false 9 | this.selectedFolder = null 10 | this.setFiles() 11 | this.folderEdit = null 12 | }, 13 | folderValue: null, 14 | 'editInputFolder': { 15 | ['x-ref']: "folderEdit", 16 | async ['@keyup.enter']() { 17 | await SaveFolderAction(this.$data) 18 | }, 19 | async ['@keyup.esc']() { 20 | this.removeInput() 21 | }, 22 | async ['@click.outside']() { 23 | this.removeInput() 24 | }, 25 | }, 26 | 'validateInput': { 27 | async ['@click.prevent.stop']() { 28 | await SaveFolderAction(this.$data) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Support/ArraySupport.js: -------------------------------------------------------------------------------- 1 | export function arraySupport() { 2 | return { 3 | deleteItemEditFolder() { 4 | let item = this.getEditItem() 5 | item.shift() 6 | this.editFolder = null 7 | this.folderEdit = null 8 | }, 9 | async deleteFolder(parent, id) { 10 | if (!parent) { 11 | this.items.forEach((value,index)=>{ 12 | if (value.id === id) { 13 | this.items.splice(index, 1) 14 | } 15 | }) 16 | return 17 | } 18 | await this.findById(parent, id) 19 | 20 | }, 21 | async findById(parent = null, id) { 22 | if (!parent) return this.items 23 | 24 | return this.items.map((value) => { 25 | if (value.id === parent) { 26 | 27 | if (value.children.length) { 28 | value.children.forEach((v, index) => { 29 | if (v.id === id) { 30 | value.children.splice(index, 1) 31 | } 32 | }) 33 | } 34 | return value 35 | } 36 | if (value.children) { 37 | let arr = arraySupport() 38 | arr.items = value.children 39 | arr = arr.findById(parent, id) 40 | if (arr) { 41 | return value.children 42 | } 43 | } 44 | 45 | }) 46 | }, 47 | getEditFolder() { 48 | return this.getEditItem().slice(-1)[0] 49 | }, 50 | getEditItem() { 51 | if (this.parentEditFolder && this.parentEditFolder.children) { 52 | return this.parentEditFolder.children 53 | } 54 | 55 | return this.items 56 | }, 57 | editFolder: null, 58 | parentEditFolder: null, 59 | items: [], 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Support/FileSize.js: -------------------------------------------------------------------------------- 1 | export function getFileSizeForHuman() { 2 | return{ 3 | divideBy : 1000, 4 | sizeType : ['ko','Mo'], 5 | getSize(size){ 6 | 7 | }, 8 | getSizeDevideBy(size){ 9 | let calcSize = Math.ceil(size/this.divideBy) 10 | console.log(calcSize % this.divideBy,calcSize) 11 | /* if(calcSize % this.divideBy){ 12 | calcSize = this.getSizeDevideBy(calcSize) 13 | }*/ 14 | return calcSize 15 | } 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Support/FolderSupport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Delete tree with input folder 3 | * @param {Object} parentEditFolder 4 | */ 5 | export function deleteItemEditFolder(parentEditFolder) { 6 | let item = getEditItem(parentEditFolder) 7 | item.shift() 8 | this.editFolder = null 9 | this.folderEdit = null 10 | } 11 | 12 | export function getEditItem(parentEditFolder) { 13 | if (parentEditFolder && parentEditFolder.children) { 14 | return parentEditFolder.children 15 | } 16 | 17 | return parentEditFolder 18 | } 19 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Support/ImageSupport.js: -------------------------------------------------------------------------------- 1 | import {Warning} from "./../Support/Notification"; 2 | 3 | export function ImageSupport() { 4 | return { 5 | mimeType: [ 6 | 'application/pdf', 7 | 'application/wps-office.xlsx', 8 | 'application/wps-office.xls', 9 | 'application/wps-office.docx', 10 | 'application/wps-office.doc', 11 | 'image/jpeg', 12 | 'image/png', 13 | ], 14 | fileSize : 4096000, 15 | error : null, 16 | progress : 0, 17 | /** 18 | * 19 | * @param {File[]} files 20 | * @return {File[]} 21 | */ 22 | getDesireFiles(files){ 23 | files = Array.from(files).filter((file)=>{ 24 | if(file.size > this.fileSize) { 25 | Warning(`Le fichier ${file.name} doit être inférieur à ${Math.round(this.fileSize/1000000)}Mo `) 26 | return false 27 | } 28 | if (!this.mimeType.find(type => type === file.type)) { 29 | Warning(`L'extension du fichier ${file.name} (${file.type}) est Invalide`) 30 | return false 31 | } 32 | return true 33 | }) 34 | return files 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Support/Notification.js: -------------------------------------------------------------------------------- 1 | import {Icons} from "./../Icons/Icons"; 2 | 3 | /** 4 | * 5 | * @param {String} message 6 | * @param {null|String}type 7 | * @return {{wrapperId: null, getModal(): string, count: number, show(): void, message :string, type: string, danger(): notificationBuilder, getModalWrapper(): string, delay: number, widthVar: string, success(): notificationBuilder, warning(): notificationBuilder, Id: null, setDelay({Number}): notificationBuilder, info(): notificationBuilder}|*|string} 8 | */ 9 | export function notificationBuilder(message, type = 'success') { 10 | return { 11 | message, 12 | type, 13 | info() { 14 | this.type = 'info' 15 | return this 16 | } 17 | , success() { 18 | this.type = 'success' 19 | return this 20 | }, 21 | danger() { 22 | this.type = 'danger' 23 | return this 24 | } 25 | , warning() { 26 | this.type = 'warning' 27 | return this 28 | }, 29 | setDelay(delay) { 30 | this.delay = delay 31 | return this 32 | }, 33 | count: 100, 34 | wrapperId: null, 35 | Id: null, 36 | delay: 4000, 37 | getModalWrapper() { 38 | this.wrapperId = `webplus-${Math.round(Math.random() * 100)}` 39 | return `
` 40 | }, 41 | widthVar : null, 42 | getModal() { 43 | this.Id = `webplus-${Math.random().toString(36).slice(2)}` 44 | this.widthVar = '--' + this.Id + '-width' 45 | return `
46 |
${Icons()[this.type]}
47 |
${this.message}
48 | 49 |
` 50 | }, 51 | show() { 52 | if (!document.getElementById('webplus-m-modal_wrapper')) { 53 | document.body.insertAdjacentHTML('beforeend', this.getModalWrapper()) 54 | } 55 | document.getElementById('webplus-m-modal_wrapper').insertAdjacentHTML('beforeend', this.getModal()) 56 | let i = 1 57 | const el = document.getElementById(this.Id), 58 | interval_id = setInterval(() => { 59 | this.count = 100 - Math.ceil(((i * 10) / this.delay) * 100) 60 | el.style.setProperty(this.widthVar,this.count + "%") 61 | i++ 62 | }, 10) 63 | setTimeout(() => { 64 | el.remove() 65 | this.Id = null 66 | clearInterval(interval_id) 67 | }, this.delay) 68 | } 69 | 70 | } 71 | } 72 | export function Success(message) { 73 | notificationBuilder(message).success().show() 74 | } 75 | export function Info(message) { 76 | notificationBuilder(message).info().show() 77 | } 78 | export function Danger(message) { 79 | notificationBuilder(message).danger().show() 80 | } 81 | export function Warning(message) { 82 | notificationBuilder(message).warning().show() 83 | } 84 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/Thumbnail/Thumbnail.js: -------------------------------------------------------------------------------- 1 | import {Success} from "./../Support/Notification"; 2 | import {DeleteFile} from "./../Actions/FileAction"; 3 | 4 | 5 | /** 6 | * 7 | * @param {Object} file 8 | * @param {Number} index 9 | * @return {{fuck, deletingFile: {"@click"(): void}, imgTransition: {":show"(): void}, showImg: boolean}} 10 | */ 11 | export function thumbnail(file,index) { 12 | return { 13 | showImg : false, 14 | file, 15 | index, 16 | /* getSize(){ 17 | return getFileSizeForHuman().getSizeDevideBy(file.size) + 'Ko' 18 | },*/ 19 | 'imgTransition':{ 20 | //Transition after load an image 21 | [':show'](){ 22 | setTimeout(()=>{ 23 | this.showImg = true 24 | },5) 25 | } 26 | }, 27 | 'deletingFile':{ 28 | async ['@click']() { 29 | DeleteFile(this.$data) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/dist/js/Components/Filemanager/functions/Ui.js: -------------------------------------------------------------------------------- 1 | import {Icons} from "./../Icons/Icons"; 2 | 3 | export function ui(is_folder) { 4 | let divPlus = `
5 | ${Icons().plus } 6 |
` 7 | let divNewFolder = `
8 | ${Icons().enter} 9 |
` 10 | 11 | if (is_folder){ 12 | return divPlus 13 | } 14 | return divNewFolder 15 | } 16 | -------------------------------------------------------------------------------- /resources/dist/js/filemanager.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs'; 2 | import {Filemanager} from "./Components/Filemanager/Filemanager"; 3 | import {folder} from "./Components/Filemanager/Folder"; 4 | import {inputEditFolder} from "./Components/Filemanager/InputEditFolder"; 5 | import {dragableFiles} from "./Components/Filemanager/DragableFiles"; 6 | import {thumbnail} from "./Components/Filemanager/Thumbnail/Thumbnail"; 7 | import {WbFile} from "./Components/Filemanager/File"; 8 | 9 | 10 | Alpine.data('Filemanager',Filemanager) 11 | Alpine.data('Folder',folder) 12 | Alpine.data('inputEditFolder',inputEditFolder) 13 | Alpine.data('dragableFiles',dragableFiles) 14 | Alpine.data('thumbnail',thumbnail) 15 | Alpine.data('WbFile',WbFile) 16 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webplusmultimedia/FileManager/d881e4d8fe2927524fc74d958a61838bd10d9038/resources/views/.gitkeep -------------------------------------------------------------------------------- /resources/views/components/filemanager/files-up-loader.blade.php: -------------------------------------------------------------------------------- 1 |
class(['relative flex justify-center items-center p-6 text-lg']) }} 3 | x-bind="overZone" 4 | x-show="showZoneFiles" 5 | > 6 | 7 |
8 |

9 | Ce répertoire est vide 10 |

11 |

12 | Déposez un fichier ici pour le téléverser 13 |

14 | 20 |
21 | 22 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /resources/views/components/filemanager/folder.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'folder', 3 | 'parent'=>null 4 | ]) 5 |
6 |
class(['folder group duration-300 py-1']) }} :class="{ 'active' : active?? null }"> 7 |
8 | 9 | {{ count($folder)?$folder['file']:'/' }} 10 |
11 | 12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /resources/views/components/filemanager/folders.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'folders', 3 | 'parent'=>null 4 | ]) 5 |
6 | @foreach($folders as $folder) 7 | 8 | @if($folder && isset($folder['children'])) 9 | 10 | @endif 11 | @endforeach 12 |
13 | -------------------------------------------------------------------------------- /resources/views/components/filemanager/thumbnail.blade.php: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /resources/views/components/icons/folder.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/icons/plus.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/components/livewire/file-manager/uploader.blade.php: -------------------------------------------------------------------------------- 1 |
7 |
8 |
9 |
10 | 14 |
15 |
16 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/Commands/FileManagerCommand.php: -------------------------------------------------------------------------------- 1 | comment('All done'); 16 | 17 | return self::SUCCESS; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Facades/FileManager.php: -------------------------------------------------------------------------------- 1 | name('filemanager') 21 | ->hasConfigFile() 22 | ->hasAssets() 23 | ->hasViews(); 24 | } 25 | 26 | public function packageBooted(): void 27 | { 28 | Livewire::component('filemanager', Uploader::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/CreateFolderAction.php: -------------------------------------------------------------------------------- 1 | exists($root)) { 18 | Storage::disk('public')->makeDirectory($root); 19 | 20 | return $this->message(__("Le répertoire {$name} a été crée"))->success(FolderDTO::createFromFolderName($dir)->toArray()); 21 | } 22 | 23 | return $this->message(__("Le répertoire {$name} existe ({$root})"))->error(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/DeleteFile.php: -------------------------------------------------------------------------------- 1 | exists($root)) { 15 | Storage::disk('public')->delete($root); 16 | 17 | return $this->message(__("L'image {$file} a été supprimé"))->success(); 18 | } 19 | 20 | return $this->message(__("L'image {$file} n'a pas été supprimé"))->error(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/DeleteFolder.php: -------------------------------------------------------------------------------- 1 | exists($root) and empty(Storage::disk('public')->files($root)) and empty(Storage::disk('public')->directories($root))) { 15 | Storage::disk('public')->deleteDirectory($root); 16 | 17 | return $this->message("le répertoire {$folder} a été supprimé")->success(); 18 | } 19 | 20 | return $this->message("Impossible de supprimer le répertoire {$folder}.
Ce répertoire n'est pas vide")->error(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/FetchFiles.php: -------------------------------------------------------------------------------- 1 | files($root)) { 16 | return $this->transform($files); 17 | } 18 | 19 | return []; 20 | } 21 | 22 | private function transform(array $files): array 23 | { 24 | $results = []; 25 | foreach ($files as $file) { 26 | $results[] = FileDTO::createFromFile($file); 27 | } 28 | 29 | return $results; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/FetchFolders.php: -------------------------------------------------------------------------------- 1 | directories($root)) { 16 | return $this->allFolders($folders); 17 | } 18 | 19 | return []; 20 | } 21 | 22 | private function allFolders(array $folders): array 23 | { 24 | $result = []; 25 | foreach ($folders as $folder) { 26 | $result[] = FolderDTO::createFromFolderName($folder); 27 | } 28 | 29 | return $result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/HasResponseMessageAction.php: -------------------------------------------------------------------------------- 1 | message = $message; 16 | 17 | return $this; 18 | } 19 | 20 | protected function success(null|array $value = null): array 21 | { 22 | $this->error = false; 23 | $result = ['error' => $this->error, 'message' => $this->message]; 24 | if ($value) { 25 | $result['result'] = $value; 26 | } 27 | 28 | return $result; 29 | } 30 | 31 | protected function error(null|array $value = null): array 32 | { 33 | $this->error = true; 34 | $result = ['error' => $this->error, 'message' => $this->message]; 35 | if ($value) { 36 | $result['result'] = $value; 37 | } 38 | 39 | return $result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Actions/SaveFiles.php: -------------------------------------------------------------------------------- 1 | storeAs($root, $file->getClientOriginalName(), 'public'); 22 | if ($path) { 23 | $results[] = FileDTO::createFromFile($path)->toArray(); 24 | } 25 | $file->delete(); 26 | } 27 | 28 | return $this->message(__('Les images ont été importées'))->success(['files' => $results]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/DTOs/FileDTO.php: -------------------------------------------------------------------------------- 1 | url($file), size: Storage::disk('public')->size($file)); 26 | } 27 | 28 | public function toArray(): array 29 | { 30 | return [$this]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/DTOs/FolderDTO.php: -------------------------------------------------------------------------------- 1 | remove($root.'/')->value(), 26 | name: $pathInfo['filename'], 27 | parent: $parent ? str($parent)->remove($root.'/')->value() : null 28 | ); 29 | } 30 | 31 | public function toArray(): array 32 | { 33 | return (array) $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Livewire/FileManager/Uploader.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public $photos; 25 | 26 | protected array $rules = [ 27 | 'photos.*' => 'file|mimes:pdf,docx,doc,xlsx,xls,jpg,png|max:4096', 28 | ]; 29 | 30 | /** 31 | * @param TemporaryUploadedFile $value 32 | */ 33 | public function updatedPhotos($value): void 34 | { 35 | $this->validateOnly('photos'); 36 | } 37 | 38 | public function getFolders(string $parent): array 39 | { 40 | return (new FetchFolders())->handle($parent); 41 | } 42 | 43 | public function createFolder(string $name, null|string $parent = null): array 44 | { 45 | return (new CreateFolderAction())->handle($name, $parent); 46 | } 47 | 48 | public function deleteFolder(string $folder): array 49 | { 50 | return (new DeleteFolder())->handle($folder); 51 | } 52 | 53 | public function getFiles(null|string $folder = null): array 54 | { 55 | return (new FetchFiles())->handle($folder); 56 | } 57 | 58 | public function saveFiles(null|string $folder = null): array 59 | { 60 | $this->validate(); 61 | 62 | return (new SaveFiles())->handle($this->photos, $folder); 63 | } 64 | 65 | public function deleteFile(string $file): array 66 | { 67 | return (new DeleteFile())->handle($file); 68 | } 69 | 70 | public function render(FetchFolders $fetch): \Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\Foundation\Application 71 | { 72 | return view('filemanager::components.livewire.file-manager.uploader', ['folders' => $fetch->handle()]); 73 | } 74 | } 75 | --------------------------------------------------------------------------------