mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-05 18:23:48 +00:00
Merge remote-tracking branch 'origin/folders_upload' into folder_upload_v2
# Conflicts: # composer.lock # config/language-translations.php # public/mix-manifest.json # resources/js/components/FilesView/DesktopToolbar.vue # resources/js/helpers.js # resources/js/store/modules/fileFunctions.js # src/Domain/Files/Actions/UploadFileAction.php # src/Domain/Files/Requests/UploadRequest.php # tests/Domain/Admin/AdminTest.php # tests/Domain/Files/FileTest.php # tests/Domain/Folders/FolderTest.php # tests/Domain/Sharing/VisitorManipulatingTest.php # tests/Domain/Traffic/TrafficTest.php # tests/Domain/Trash/TrashTest.php # tests/Domain/Zip/UserZippingTest.php
This commit is contained in:
@@ -750,5 +750,6 @@ return [
|
||||
'sharelink.share_via_email' => 'Share Link on Emails',
|
||||
'sharelink.copy_embed' => 'Copy Web Insert Code',
|
||||
'popup.move_into_team_disclaimer' => 'Your folder <b class="text-theme dark-text-theme">will be moved</b> into Team Folders.',
|
||||
'actions.upload_folder' => 'Upload folder',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -35,8 +35,9 @@
|
||||
<ToolbarButton @click.stop.native="showCreateMenu" source="cloud-plus" :action="$t('actions.create')" />
|
||||
|
||||
<PopoverItem name="desktop-create" side="left">
|
||||
<OptionGroup>
|
||||
<OptionUpload :class="{'is-inactive': canUploadInView || isTeamFolderHomepage }" :title="$t('actions.upload')" />
|
||||
<OptionGroup :class="{'is-inactive': canUploadInView || !hasCapacity }">
|
||||
<OptionUpload :title="$t('actions.upload')" type="file" />
|
||||
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
|
||||
</OptionGroup>
|
||||
<OptionGroup>
|
||||
<Option @click.stop.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" />
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<svg class="alphabet-icon" fill="none" stroke="currentColor" stroke-width="2" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<svg class="alphabet-icon" fill="none" stroke="currentColor" stroke-width="2" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" :width="`${size}px`" :height="`${size}px`" viewBox="-2 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<polyline id="Path" points="11.1999993 13.1999991 5.59999967 0.199999094 0 13.1999991 5.59999967 0.199999094"></polyline>
|
||||
<line x1="2.25" y1="8" x2="8.75" y2="8" id="Line-2"></line>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['size']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.alphabet-icon {
|
||||
|
||||
25
resources/js/components/FilesView/Icons/FolderUploadIcon.vue
Normal file
25
resources/js/components/FilesView/Icons/FolderUploadIcon.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<svg class="preview-list-icon" fill="none" stroke="currentColor" stroke-width="1.5" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" :width="`${size}px`" :height="`${size}px`" viewBox="0 -2 14 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M0,10.6420028 C0,8.60583431 0,5.5515816 0,1.47924466 C0,0.662280392 0.633305625,0 1.4145277,0 L4.95084696,0 L6.36537467,2.21886699 L12.7307493,2.21886699 C13.5119714,2.21886699 14.145277,2.88114738 14.145277,3.69811164 C14.145277,7.76603445 14.145277,7.76603445 14.145277,11.8339573 C14.145277,12.6509215 13.5119714,13.3132019 12.7307493,13.3132019 C11.9928651,13.3132019 12.1671651,13.3132019 11.798223,13.3132019" id="Path"></path>
|
||||
<polyline id="Path-Copy-8" points="9.49893123 9.53496452 6.74946561 6.60112928 4 9.53496452"></polyline>
|
||||
<line x1="6.74946561" y1="6.60112928" x2="6.74946561" y2="13.2022586" id="Path-Copy-7"></line>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['size']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.preview-list-icon {
|
||||
|
||||
path,
|
||||
line,
|
||||
polyline {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,37 @@
|
||||
<template>
|
||||
<label for="file" class="menu-option group">
|
||||
<label class="menu-option group">
|
||||
<div class="icon-left group-hover-text-theme">
|
||||
<upload-cloud-icon size="17" class="group-hover-text-theme"/>
|
||||
<upload-cloud-icon v-if="type === 'file'" size="17" class="group-hover-text-theme"/>
|
||||
<folder-upload-icon v-if="type === 'folder'" size="17" class="group-hover-text-theme"/>
|
||||
</div>
|
||||
<div class="text-label group-hover-text-theme">
|
||||
{{ title }}
|
||||
<input
|
||||
@change="emmitFiles"
|
||||
v-show="false"
|
||||
id="file"
|
||||
type="file"
|
||||
name="files[]"
|
||||
multiple
|
||||
/>
|
||||
<input
|
||||
v-if="type === 'file'"
|
||||
@change="emmitFiles"
|
||||
v-show="false"
|
||||
id="file"
|
||||
type="file"
|
||||
name="files[]"
|
||||
multiple
|
||||
/>
|
||||
|
||||
<input
|
||||
v-if="type === 'folder'"
|
||||
@change="emmitFiles"
|
||||
v-show="false"
|
||||
id="folder"
|
||||
type="file"
|
||||
name="folders[]"
|
||||
webkitdirectory
|
||||
mozdirectory
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FolderUploadIcon from '/resources/js/components/FilesView/Icons/FolderUploadIcon'
|
||||
import {events} from '/resources/js/bus'
|
||||
import {
|
||||
UploadCloudIcon,
|
||||
@@ -26,10 +40,11 @@ import {
|
||||
export default {
|
||||
name: 'Option',
|
||||
props:[
|
||||
'title',
|
||||
'title', 'type'
|
||||
],
|
||||
components: {
|
||||
UploadCloudIcon,
|
||||
FolderUploadIcon,
|
||||
},
|
||||
methods: {
|
||||
emmitFiles(e) {
|
||||
@@ -71,12 +86,20 @@ import {
|
||||
.text-label {
|
||||
@include font-size(16);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $light_background;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
||||
.menu-option {
|
||||
color: $dark_mode_text_primary;
|
||||
|
||||
&:hover {
|
||||
background: lighten($dark_mode_foreground, 2%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
resources/js/helpers.js
vendored
Normal file
0
resources/js/helpers.js
vendored
Normal file
8
resources/js/store/modules/fileFunctions.js
vendored
8
resources/js/store/modules/fileFunctions.js
vendored
@@ -152,7 +152,7 @@ const actions = {
|
||||
})
|
||||
.catch(() => Vue.prototype.$isSomethingWrong())
|
||||
},
|
||||
uploadFiles: ({commit, getters}, {form, fileSize, totalUploadedSize}) => {
|
||||
uploadFiles: ({commit, getters, dispatch}, {form, fileSize, totalUploadedSize}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Get route
|
||||
@@ -210,8 +210,12 @@ const actions = {
|
||||
}
|
||||
|
||||
// Reset upload process
|
||||
if (!getters.fileQueue.length)
|
||||
if (!getters.fileQueue.length) {
|
||||
commit('CLEAR_UPLOAD_PROGRESS')
|
||||
|
||||
// Reload files after upload is done
|
||||
dispatch('getFolder', [{folder: getters.currentFolder, back: true, init: false}])
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
126
src/Domain/Files/Actions/CreateFolderStructureAction.php
Normal file
126
src/Domain/Files/Actions/CreateFolderStructureAction.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Domain\Files\Actions;
|
||||
|
||||
use Domain\Folders\Models\Folder;
|
||||
|
||||
|
||||
class CreateFolderStructureAction
|
||||
{
|
||||
/**
|
||||
* Create a new action instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Prepare the action for execution, leveraging constructor injection.
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke($path, $parent, $user_id)
|
||||
{
|
||||
|
||||
$folders = array_slice(explode('/', $path), 1, -1);
|
||||
|
||||
$parent_id = $parent;
|
||||
|
||||
$last_folder = $parent;
|
||||
|
||||
// Get already created structure of the file parents
|
||||
$structure = Folder::whereIn('name', $folders)->with('parent')->get();
|
||||
|
||||
// If file have some parent folders
|
||||
if( count($folders) > 0) {
|
||||
|
||||
// If uploading structure has same lenght as a already existed structure
|
||||
if( count($folders) === count($structure) ) {
|
||||
|
||||
// Get correct file parent from the already craeted structure
|
||||
$last_folder = $this->get_file_parent($structure, $folders);
|
||||
|
||||
} else if ( count($folders) !== count($structure) ) {
|
||||
|
||||
|
||||
if( count($structure) > 0 ) {
|
||||
|
||||
// Check what folders are missed in structure and return missed folder with last created folder in structure
|
||||
$data = $this->check_exist_folders($structure, $folders);
|
||||
|
||||
$folders = $data[0];
|
||||
|
||||
$parent_id = $data[1];
|
||||
|
||||
}
|
||||
|
||||
// Create folders
|
||||
foreach($folders as $folder) {
|
||||
|
||||
$new_folder = Folder::create([
|
||||
'name' => $folder,
|
||||
'parent_id' => $parent_id,
|
||||
'user_id' => $user_id,
|
||||
]);
|
||||
|
||||
$parent_id = $new_folder->id;
|
||||
|
||||
$last_folder = $new_folder->id;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return $last_folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the file parent folder in already existed structure
|
||||
*/
|
||||
private function get_file_parent($structure, $folders)
|
||||
{
|
||||
$parent_name = '';
|
||||
|
||||
foreach(array_reverse($folders) as $folder) {
|
||||
|
||||
$item = $structure->where('name', $folder);
|
||||
|
||||
$parent = $item->pluck('parent')->pluck('name')[0];
|
||||
|
||||
// Check if folder have valid parent name
|
||||
if( $parent && $folder === $parent_name || $parent_name == '') {
|
||||
|
||||
$parent_name = $parent;
|
||||
}
|
||||
|
||||
return $structure->where('name', $folders[array_key_last($folders)])->first()->id;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the folders that is need to create in already created structure and last created parent
|
||||
*/
|
||||
private function check_exist_folders($structure, $folders)
|
||||
{
|
||||
|
||||
$create_folders = [];
|
||||
$last_parent = '';
|
||||
|
||||
foreach($folders as $folder) {
|
||||
|
||||
// Filter folders that is need to create
|
||||
if(! $structure->where('name', $folder)->first()) {
|
||||
|
||||
array_push($create_folders, $folder);
|
||||
}else {
|
||||
|
||||
// Find last created folder
|
||||
$last_parent = $structure->where('name', $folder)->first()->id;
|
||||
}
|
||||
}
|
||||
|
||||
return [$create_folders, $last_parent];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ use Domain\Files\Requests\UploadRequest;
|
||||
use Domain\Files\Models\File as UserFile;
|
||||
use Domain\Traffic\Actions\RecordUploadAction;
|
||||
use App\Users\Exceptions\InvalidUserActionException;
|
||||
use Domain\Files\Actions\CreateFolderStructureAction;
|
||||
|
||||
class UploadFileAction
|
||||
{
|
||||
public function __construct(
|
||||
public RecordUploadAction $recordUpload,
|
||||
public ProcessImageThumbnailAction $createImageThumbnail,
|
||||
public CreateFolderStructureAction $createFolderStructure,
|
||||
public MoveFileToExternalStorageAction $moveFileToExternalStorage,
|
||||
) {
|
||||
}
|
||||
@@ -93,6 +95,7 @@ class UploadFileAction
|
||||
'mimetype' => get_file_type_from_mimetype($file_mimetype),
|
||||
'type' => get_file_type($file_mimetype),
|
||||
'parent_id' => $request->input('parent_id'),
|
||||
'parent_id' => ($this->createFolderStructure)($request->input('path'), $request->input('parent_id'), $user_id),
|
||||
'metadata' => $metadata,
|
||||
'name' => $request->input('filename'),
|
||||
'basename' => $fileName,
|
||||
|
||||
@@ -26,6 +26,7 @@ class UploadRequest extends FormRequest
|
||||
return [
|
||||
'filename' => 'required|string',
|
||||
'parent_id' => 'nullable|uuid',
|
||||
'path' => 'required|string',
|
||||
'is_last' => 'sometimes|string',
|
||||
'file' => ['required', 'file', new DisabledMimetypes],
|
||||
];
|
||||
|
||||
@@ -304,6 +304,7 @@ class AdminTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ class FileTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
@@ -84,6 +85,7 @@ class FileTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
@@ -128,6 +130,7 @@ class FileTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(401);
|
||||
|
||||
@@ -158,6 +161,7 @@ class FileTest extends TestCase
|
||||
->postJson('/api/upload', [
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(422);
|
||||
|
||||
@@ -292,6 +296,7 @@ class FileTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
|
||||
@@ -374,6 +374,7 @@ class FolderTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
@@ -412,4 +413,58 @@ class FolderTest extends TestCase
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_upload_folders_structure_with_files()
|
||||
{
|
||||
$file_1 = UploadedFile::fake()
|
||||
->create('fake-file_1.pdf', 12000000, 'application/pdf');
|
||||
|
||||
$file_2 = UploadedFile::fake()
|
||||
->create('fake-file_2.pdf', 12000000, 'application/pdf');
|
||||
|
||||
$user = User::factory(User::class)
|
||||
->create();
|
||||
|
||||
$uploaded_file_1 = $this
|
||||
->actingAs($user)
|
||||
->postJson('/api/upload', [
|
||||
'filename' => $file_2->name,
|
||||
'file' => $file_2,
|
||||
'path' => '/Folder_1/' . $file_2->name,
|
||||
'folder_id' => null,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
$uploaded_file_2 = $this
|
||||
->actingAs($user)
|
||||
->postJson('/api/upload', [
|
||||
'filename' => $file_1->name,
|
||||
'file' => $file_1,
|
||||
'path' => '/Folder_1/Folder_2/' . $file_1->name,
|
||||
'folder_id' => null,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
|
||||
$file_1_parent = Folder::whereName('Folder_1')->first();
|
||||
|
||||
$file_2_parent = Folder::whereName('Folder_2')->first();
|
||||
|
||||
$this->assertDatabaseHas('folders', [
|
||||
'id' => $uploaded_file_1['folder_id'],
|
||||
'parent_id' => null,
|
||||
'id' => $uploaded_file_2['folder_id'],
|
||||
'parent_id' => $file_1_parent->id,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('files', [
|
||||
'id' => $uploaded_file_1['id'],
|
||||
'folder_id' => $file_1_parent->id,
|
||||
'id' => $uploaded_file_2['id'],
|
||||
'folder_id' => $file_2_parent->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +333,7 @@ class VisitorManipulatingTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
}
|
||||
@@ -343,6 +344,7 @@ class VisitorManipulatingTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class TrafficTest extends TestCase
|
||||
'filename' => $this->file->name,
|
||||
'file' => $this->file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $this->file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
@@ -60,6 +61,7 @@ class TrafficTest extends TestCase
|
||||
'filename' => $this->file->name,
|
||||
'file' => $this->file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $this->file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
@@ -80,6 +82,7 @@ class TrafficTest extends TestCase
|
||||
'filename' => $secondFile->name,
|
||||
'file' => $secondFile,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $secondFile->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
@@ -117,6 +120,7 @@ class TrafficTest extends TestCase
|
||||
'filename' => $this->file->name,
|
||||
'file' => $this->file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $this->file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ class TrashTest extends TestCase
|
||||
'filename' => $image->name,
|
||||
'file' => $image,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $image->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class UserZippingTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
@@ -48,6 +49,7 @@ class UserZippingTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => null,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
@@ -88,6 +90,7 @@ class UserZippingTest extends TestCase
|
||||
'filename' => $file->name,
|
||||
'file' => $file,
|
||||
'parent_id' => $folder->id,
|
||||
'path' => '/' . $file->name,
|
||||
'is_last' => 'true',
|
||||
])->assertStatus(201);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user