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:
Čarodej
2022-01-24 08:05:04 +01:00
17 changed files with 277 additions and 16 deletions

View File

@@ -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',
],
];

View File

@@ -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" />

View File

@@ -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 {

View 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>

View File

@@ -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
View File

View File

@@ -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 => {

View 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];
}
}

View File

@@ -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,

View File

@@ -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],
];

View File

@@ -304,6 +304,7 @@ class AdminTest extends TestCase
'filename' => $file->name,
'file' => $file,
'parent_id' => null,
'path' => '/' . $file->name,
'is_last' => 'true',
])->assertStatus(201);
});

View File

@@ -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);
});

View File

@@ -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,
]);
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -78,6 +78,7 @@ class TrashTest extends TestCase
'filename' => $image->name,
'file' => $image,
'parent_id' => null,
'path' => '/' . $image->name,
'is_last' => 'true',
])->assertStatus(201);

View File

@@ -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);
});