Version 1.3

- i18n localization support
- Added SK, EN language files
- Video/Audio preview in file preview panel (Thanks to Joshua Fouyon to participating on this feature)
- Drop uploading (You can now drag files from desktop and drop it to VueFileManager)
- Fixed bug when rename item in safari browser
- Fixed bug when you drag folder from trash to favourites in sidebar panel
- small functions and design improvements
This commit is contained in:
MakingCG
2020-04-03 11:52:54 +02:00
parent 96da39923d
commit 4504276563
28 changed files with 1558 additions and 936 deletions
@@ -428,7 +428,7 @@ class FileManagerController extends Controller
// File // File
$filename = Str::random() . '-' . str_replace(' ', '', $file->getClientOriginalName()); $filename = Str::random() . '-' . str_replace(' ', '', $file->getClientOriginalName());
$filetype = 'file'; $filetype = get_file_type($file);
$thumbnail = null; $thumbnail = null;
$filesize = $file->getSize(); $filesize = $file->getSize();
$directory = 'file-manager'; $directory = 'file-manager';
@@ -442,9 +442,8 @@ class FileManagerController extends Controller
Storage::disk('local')->putFileAs($directory, $file, $filename, 'public'); Storage::disk('local')->putFileAs($directory, $file, $filename, 'public');
// Create image thumbnail // Create image thumbnail
if (substr($file->getMimeType(), 0, 5) == 'image') { if ( $filetype == 'image' ) {
$filetype = 'image';
$thumbnail = 'thumbnail-' . $filename; $thumbnail = 'thumbnail-' . $filename;
// Create intervention image // Create intervention image
@@ -562,6 +561,8 @@ class FileManagerController extends Controller
$response->header("Content-Type", $type); $response->header("Content-Type", $type);
$response->header("Content-Disposition", 'attachment; filename=' . $filename); $response->header("Content-Disposition", 'attachment; filename=' . $filename);
$response->header("Content-Length", $size); $response->header("Content-Length", $size);
$response->header("Accept-Ranges", "bytes");
$response->header("Content-Range", "bytes 0-" . $size . "/" . $size);
return $response; return $response;
} }
+28 -1
View File
@@ -100,7 +100,8 @@ function get_storage_fill_percentage($used, $capacity)
* *
* @return string * @return string
*/ */
function user_storage_percentage() { function user_storage_percentage()
{
$user = \Illuminate\Support\Facades\Auth::user(); $user = \Illuminate\Support\Facades\Auth::user();
@@ -172,3 +173,29 @@ function format_date($date, $format = '%d. %B. %Y, %H:%M')
return $start->formatLocalized($format); return $start->formatLocalized($format);
} }
/**
* Get file type from mimetype
*
* @param $file
* @return string
*/
function get_file_type($file)
{
// Get mimetype from file
$mimetype = explode('/', $file->getMimeType());
switch ($mimetype[0]) {
case 'image':
return 'image';
break;
case 'video':
return 'video';
break;
case 'audio':
return 'audio';
break;
default:
return 'file';
}
}
+1
View File
@@ -10,6 +10,7 @@
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"askedio/laravel-soft-cascade": "^6.0", "askedio/laravel-soft-cascade": "^6.0",
"doctrine/dbal": "^2.10",
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.0",
"fruitcake/laravel-cors": "^1.0", "fruitcake/laravel-cors": "^1.0",
"gabrielelana/byte-units": "^0.5.0", "gabrielelana/byte-units": "^0.5.0",
Generated
+460 -200
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ChangeTypeAttributeInFileManagerFilesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('file_manager_files', function (Blueprint $table) {
DB::statement('ALTER TABLE file_manager_files MODIFY type TEXT;');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('file_manager_files', function (Blueprint $table) {
//
});
}
}
@@ -7,7 +7,7 @@
# #
# Host: 127.0.0.1 (MySQL 5.7.25) # Host: 127.0.0.1 (MySQL 5.7.25)
# Database: file-manager # Database: file-manager
# Generation Time: 2020-03-14 17:32:56 +0000 # Generation Time: 2020-04-03 07:57:39 +0000
# ************************************************************ # ************************************************************
@@ -64,7 +64,7 @@ CREATE TABLE `file_manager_files` (
`basename` text COLLATE utf8mb4_unicode_ci, `basename` text COLLATE utf8mb4_unicode_ci,
`mimetype` text COLLATE utf8mb4_unicode_ci, `mimetype` text COLLATE utf8mb4_unicode_ci,
`filesize` text COLLATE utf8mb4_unicode_ci, `filesize` text COLLATE utf8mb4_unicode_ci,
`type` enum('image','file') COLLATE utf8mb4_unicode_ci DEFAULT NULL, `type` text COLLATE utf8mb4_unicode_ci,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -100,7 +100,7 @@ DROP TABLE IF EXISTS `migrations`;
CREATE TABLE `migrations` ( CREATE TABLE `migrations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`batch` int(11) NOT NULL, `batch` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -122,7 +122,8 @@ VALUES
(10,'2019_08_19_000000_create_failed_jobs_table',1), (10,'2019_08_19_000000_create_failed_jobs_table',1),
(11,'2020_03_03_065147_add_user_id_to_file_manager_files_table',2), (11,'2020_03_03_065147_add_user_id_to_file_manager_files_table',2),
(12,'2020_03_03_065155_add_user_id_to_file_manager_folders_table',2), (12,'2020_03_03_065155_add_user_id_to_file_manager_folders_table',2),
(13,'2020_03_03_070319_create_favourites_folders_table',3); (13,'2020_03_03_070319_create_favourites_folders_table',3),
(14,'2020_04_02_055021_change_type_attribute_in_file_manager_files_table',4);
/*!40000 ALTER TABLE `migrations` ENABLE KEYS */; /*!40000 ALTER TABLE `migrations` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -137,7 +138,7 @@ CREATE TABLE `oauth_access_tokens` (
`id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint(20) unsigned DEFAULT NULL, `user_id` bigint(20) unsigned DEFAULT NULL,
`client_id` bigint(20) unsigned NOT NULL, `client_id` bigint(20) unsigned NOT NULL,
`name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`scopes` text COLLATE utf8mb4_unicode_ci, `scopes` text COLLATE utf8mb4_unicode_ci,
`revoked` tinyint(1) NOT NULL, `revoked` tinyint(1) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
@@ -175,7 +176,7 @@ DROP TABLE IF EXISTS `oauth_clients`;
CREATE TABLE `oauth_clients` ( CREATE TABLE `oauth_clients` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned DEFAULT NULL, `user_id` bigint(20) unsigned DEFAULT NULL,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`secret` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `secret` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`redirect` text COLLATE utf8mb4_unicode_ci NOT NULL, `redirect` text COLLATE utf8mb4_unicode_ci NOT NULL,
`personal_access_client` tinyint(1) NOT NULL, `personal_access_client` tinyint(1) NOT NULL,
@@ -234,8 +235,8 @@ CREATE TABLE `oauth_refresh_tokens` (
DROP TABLE IF EXISTS `password_resets`; DROP TABLE IF EXISTS `password_resets`;
CREATE TABLE `password_resets` ( CREATE TABLE `password_resets` (
`email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
KEY `password_resets_email_index` (`email`) KEY `password_resets_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+660 -410
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -12,14 +12,14 @@
"devDependencies": { "devDependencies": {
"axios": "^0.19", "axios": "^0.19",
"cross-env": "^5.1", "cross-env": "^5.1",
"laravel-mix": "^5.0.1", "laravel-mix": "^5.0.4",
"resolve-url-loader": "^2.3.1", "resolve-url-loader": "^2.3.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26", "@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.12.0", "@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9", "@fortawesome/vue-fontawesome": "^0.1.9",
"css-element-queries": "^1.2.3", "css-element-queries": "^1.2.3",
"lodash": "^4.17.15", "lodash": "^4.17.15",
+7
View File
@@ -5,6 +5,13 @@
RewriteEngine On RewriteEngine On
AddType video/ogg .ogv
AddType video/mp4 .mp4
AddType video/webm .webm
<ifModule mod_headers.c>
Header set Connection keep-alive
</ifModule>
# Handle Authorization Header # Handle Authorization Header
RewriteCond %{HTTP:Authorization} . RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
@import url(https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700&display=swap); @import url(https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;900&display=swap);
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -37
View File
@@ -1,40 +1,4 @@
{ {
"/js/main.js": "/js/main.js", "/js/main.js": "/js/main.js",
"/css/app.css": "/css/app.css", "/css/app.css": "/css/app.css"
"/js/main.b199e742764976d477eb.hot-update.js": "/js/main.b199e742764976d477eb.hot-update.js",
"/js/main.74bd4021acf8f5e6f2c0.hot-update.js": "/js/main.74bd4021acf8f5e6f2c0.hot-update.js",
"/js/main.7751cc56f809bdcca52f.hot-update.js": "/js/main.7751cc56f809bdcca52f.hot-update.js",
"/js/main.6550285a609d98fd0a2c.hot-update.js": "/js/main.6550285a609d98fd0a2c.hot-update.js",
"/js/main.593b8d94aebec435eaa6.hot-update.js": "/js/main.593b8d94aebec435eaa6.hot-update.js",
"/js/main.9646136421cb914b9bc8.hot-update.js": "/js/main.9646136421cb914b9bc8.hot-update.js",
"/js/main.34d97ba06940b269e518.hot-update.js": "/js/main.34d97ba06940b269e518.hot-update.js",
"/js/main.93f2bdf89f84cfacf4f3.hot-update.js": "/js/main.93f2bdf89f84cfacf4f3.hot-update.js",
"/js/main.817de8c8f5fd813c607b.hot-update.js": "/js/main.817de8c8f5fd813c607b.hot-update.js",
"/js/main.963bfdadae1fb90e0238.hot-update.js": "/js/main.963bfdadae1fb90e0238.hot-update.js",
"/js/main.1512db5a6a7632fb9e8e.hot-update.js": "/js/main.1512db5a6a7632fb9e8e.hot-update.js",
"/js/main.3a19340ddc14dcc839a1.hot-update.js": "/js/main.3a19340ddc14dcc839a1.hot-update.js",
"/js/main.397553844e2b0ca23cc7.hot-update.js": "/js/main.397553844e2b0ca23cc7.hot-update.js",
"/js/main.c87eeede975b87bf2b16.hot-update.js": "/js/main.c87eeede975b87bf2b16.hot-update.js",
"/js/main.f580b09761154926899f.hot-update.js": "/js/main.f580b09761154926899f.hot-update.js",
"/js/main.91d490a5132517514bfb.hot-update.js": "/js/main.91d490a5132517514bfb.hot-update.js",
"/js/main.bdad4fbdd1e409eb4b21.hot-update.js": "/js/main.bdad4fbdd1e409eb4b21.hot-update.js",
"/js/main.48d106f956e2b5fecbb0.hot-update.js": "/js/main.48d106f956e2b5fecbb0.hot-update.js",
"/js/main.3f7d1e2b874ad885f9ba.hot-update.js": "/js/main.3f7d1e2b874ad885f9ba.hot-update.js",
"/js/main.be94f32edd036852aa82.hot-update.js": "/js/main.be94f32edd036852aa82.hot-update.js",
"/js/main.0b838845dace73e0df6d.hot-update.js": "/js/main.0b838845dace73e0df6d.hot-update.js",
"/js/main.aac07313ac3ce9a2e597.hot-update.js": "/js/main.aac07313ac3ce9a2e597.hot-update.js",
"/js/main.37168073afe720eff29e.hot-update.js": "/js/main.37168073afe720eff29e.hot-update.js",
"/js/main.1eafca2af4cbb4242c1f.hot-update.js": "/js/main.1eafca2af4cbb4242c1f.hot-update.js",
"/js/main.8f3db6ec42d42fa7152e.hot-update.js": "/js/main.8f3db6ec42d42fa7152e.hot-update.js",
"/js/main.3d0c0e744120bafa0076.hot-update.js": "/js/main.3d0c0e744120bafa0076.hot-update.js",
"/js/main.5bf20eacf51dd037796e.hot-update.js": "/js/main.5bf20eacf51dd037796e.hot-update.js",
"/js/main.bf91b39fc6ebda933d92.hot-update.js": "/js/main.bf91b39fc6ebda933d92.hot-update.js",
"/js/main.87b79f2adeaf95ca1eae.hot-update.js": "/js/main.87b79f2adeaf95ca1eae.hot-update.js",
"/js/main.caeb9ea0fe68c0e20cbc.hot-update.js": "/js/main.caeb9ea0fe68c0e20cbc.hot-update.js",
"/js/main.61e2b39b769cf182b833.hot-update.js": "/js/main.61e2b39b769cf182b833.hot-update.js",
"/js/main.ac3b225c09da944dd637.hot-update.js": "/js/main.ac3b225c09da944dd637.hot-update.js",
"/js/main.e90426301ffd1febd2e8.hot-update.js": "/js/main.e90426301ffd1febd2e8.hot-update.js",
"/js/main.ee9680aa2ebf66f438e2.hot-update.js": "/js/main.ee9680aa2ebf66f438e2.hot-update.js",
"/js/main.0af6eb46c23ed68de4db.hot-update.js": "/js/main.0af6eb46c23ed68de4db.hot-update.js",
"/js/main.7d6e0c7874e39ca5797d.hot-update.js": "/js/main.7d6e0c7874e39ca5797d.hot-update.js"
} }
@@ -88,7 +88,6 @@
<style lang="scss"> <style lang="scss">
@import "@assets/app.scss"; @import "@assets/app.scss";
#files-view { #files-view {
font-family: 'Nunito', sans-serif; font-family: 'Nunito', sans-serif;
font-size: 16px; font-size: 16px;
@@ -161,27 +160,21 @@
margin-bottom: 10px; margin-bottom: 10px;
height: 90px; height: 90px;
&.file { .file-icon {
.file-icon { @include font-size(75);
@include font-size(75);
}
.file-icon-text {
@include font-size(12);
}
} }
&.folder { .file-icon-text {
@include font-size(14); @include font-size(12);
.folder-icon {
margin-top: 0;
margin-bottom: 0;
}
} }
&.image img { .folder-icon {
@include font-size(75);
margin-top: 0;
margin-bottom: 0;
}
.image {
width: 90px; width: 90px;
height: 90px; height: 90px;
} }
@@ -8,7 +8,7 @@
<ul class="menu-options" id="menu-options-list" ref="list" @click="closeAndResetContextMenu"> <ul class="menu-options" id="menu-options-list" ref="list" @click="closeAndResetContextMenu">
<!--View--> <!--View-->
<li class="menu-option" @click="addToFavourites" v-if="! $isTrashLocation() && item && item.type === 'folder'">{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}</li> <li class="menu-option" @click="addToFavourites" v-if="! $isTrashLocation() && item && isFolder">{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}</li>
<li class="menu-option" @click="createFolder" v-if="! $isTrashLocation()">{{ $t('context_menu.create_folder') }}</li> <li class="menu-option" @click="createFolder" v-if="! $isTrashLocation()">{{ $t('context_menu.create_folder') }}</li>
<!--Edits--> <!--Edits-->
@@ -21,7 +21,7 @@
<!--Others--> <!--Others-->
<li class="menu-option" @click="ItemDetail" v-if="item">{{ $t('context_menu.detail') }}</li> <li class="menu-option" @click="ItemDetail" v-if="item">{{ $t('context_menu.detail') }}</li>
<li class="menu-option" @click="downloadItem" v-if="isFile || isImage">{{ $t('context_menu.download') }}</li> <li class="menu-option" @click="downloadItem" v-if="! isFolder && item">{{ $t('context_menu.download') }}</li>
</ul> </ul>
</div> </div>
</template> </template>
@@ -34,11 +34,14 @@
name: 'ContextMenu', name: 'ContextMenu',
computed: { computed: {
...mapGetters(['app']), ...mapGetters(['app']),
isFolder() {
return this.item && this.item.type === 'folder'
},
isFile() { isFile() {
return this.item && this.item.type === 'file' ? true : false return (this.item && this.item.type !== 'folder') && (this.item && this.item.type !== 'image')
}, },
isImage() { isImage() {
return this.item && this.item.type === 'image' ? true : false return this.item && this.item.type === 'image'
}, },
isInFavourites() { isInFavourites() {
return this.app.favourites.find(el => el.unique_id == this.item.unique_id) return this.app.favourites.find(el => el.unique_id == this.item.unique_id)
@@ -1,31 +1,18 @@
<template> <template>
<div v-if="fileInfoDetail"> <div v-if="fileInfoDetail">
<div class="file-headline" spellcheck="false"> <div class="file-headline" spellcheck="false">
<!--Image thumbnail-->
<div v-if="fileInfoDetail.type == 'image'" class="image-preview"> <FilePreview />
<img
@dblclick="$openImageOnNewTab(fileInfoDetail.file_url)"
:src="fileInfoDetail.thumbnail"
:alt="fileInfoDetail.name"
/>
</div>
<!--File info--> <!--File info-->
<div class="flex"> <div class="flex">
<div class="icon"> <div class="icon">
<div class="icon-preview" @dblclick="getItemAction"> <div class="icon-preview" @dblclick="getItemAction">
<FontAwesomeIcon <FontAwesomeIcon v-if="fileInfoDetail.type == 'folder'" icon="folder"></FontAwesomeIcon>
v-if="fileInfoDetail.type == 'folder'" <FontAwesomeIcon v-if="fileInfoDetail.type == 'file'" icon="file"></FontAwesomeIcon>
icon="folder" <FontAwesomeIcon v-if="fileInfoDetail.type == 'image'" icon="file-image"></FontAwesomeIcon>
></FontAwesomeIcon> <FontAwesomeIcon v-if="fileInfoDetail.type == 'video'" icon="file-video"></FontAwesomeIcon>
<FontAwesomeIcon <FontAwesomeIcon v-if="fileInfoDetail.type == 'audio'" icon="file-audio"></FontAwesomeIcon>
v-if="fileInfoDetail.type == 'file'"
icon="file"
></FontAwesomeIcon>
<FontAwesomeIcon
v-if="fileInfoDetail.type == 'image'"
icon="file-image"
></FontAwesomeIcon>
</div> </div>
</div> </div>
<div class="file-info"> <div class="file-info">
@@ -64,12 +51,16 @@
</template> </template>
<script> <script>
import FilePreview from '@/components/VueFileManagerComponents/FilesView/FilePreview'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {debounce} from 'lodash' import {debounce} from 'lodash'
import {events} from "@/bus" import {events} from "@/bus"
export default { export default {
name: 'FilesInfoPanel', name: 'FileInfoPanel',
components: {
FilePreview
},
computed: { computed: {
...mapGetters(['fileInfoDetail']) ...mapGetters(['fileInfoDetail'])
}, },
@@ -122,20 +113,6 @@
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 8px; border-radius: 8px;
.image-preview {
width: 100%;
display: block;
margin-bottom: 7px;
img {
border-radius: 4px;
overflow: hidden;
width: 100%;
object-fit: cover;
cursor: pointer;
}
}
.flex { .flex {
display: flex; display: flex;
align-items: top; align-items: top;
@@ -251,6 +228,13 @@
span { span {
color: $dark_mode_text_primary color: $dark_mode_text_primary
} }
.action-button {
.icon {
color: $dark_mode_text_primary;
}
}
} }
} }
} }
@@ -19,7 +19,7 @@
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }" :class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
> >
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item" :class="data.type"> <div class="icon-item">
<!--If is file or image, then link item--> <!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ <span v-if="isFile" class="file-icon-text">{{
data.mimetype data.mimetype
@@ -29,15 +29,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--Image thumbnail--> <!--Image thumbnail-->
<img v-if="isImage" :src="data.thumbnail" :alt="data.name"/> <img v-if="isImage" class="image" :src="data.thumbnail" :alt="data.name"/>
<!--Else show only folder icon--> <!--Else show only folder icon-->
<FontAwesomeIcon <FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
v-if="isFolder"
:class="{'is-deleted': isDeleted}"
class="folder-icon"
icon="folder"
/>
</div> </div>
<!--Name--> <!--Name-->
@@ -52,7 +47,7 @@
> >
<!--Other attributes--> <!--Other attributes-->
<span v-if="isFile || isImage" class="item-size">{{ <span v-if="! isFolder" class="item-size">{{
data.filesize data.filesize
}}</span> }}</span>
@@ -82,7 +77,7 @@
return this.data.type === 'folder' return this.data.type === 'folder'
}, },
isFile() { isFile() {
return this.data.type === 'file' return this.data.type !== 'folder' && this.data.type !== 'image'
}, },
isImage() { isImage() {
return this.data.type === 'image' return this.data.type === 'image'
@@ -207,7 +202,6 @@
} }
} }
.file-wrapper { .file-wrapper {
position: relative; position: relative;
text-align: center; text-align: center;
@@ -232,6 +226,11 @@
.name { .name {
display: block; display: block;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover { &[contenteditable='true']:hover {
text-decoration: underline; text-decoration: underline;
} }
@@ -282,6 +281,7 @@
} }
.icon-item { .icon-item {
text-align: center;
position: relative; position: relative;
height: 110px; height: 110px;
margin-bottom: 20px; margin-bottom: 20px;
@@ -303,41 +303,33 @@
} }
} }
&.file { .file-icon-text {
margin: 5px auto 0;
.file-icon-text { position: absolute;
margin: 5px auto 0; text-align: center;
position: absolute; left: 0;
text-align: center; right: 0;
left: 0; color: $theme;
right: 0; font-weight: 600;
color: $theme; user-select: none;
font-weight: 600; max-width: 65px;
user-select: none; max-height: 20px;
max-width: 65px; overflow: hidden;
max-height: 20px; text-overflow: ellipsis;
overflow: hidden; white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
&.image { .image {
img { max-width: 95%;
max-width: 95%; object-fit: cover;
object-fit: cover; user-select: none;
user-select: none; height: 110px;
height: 110px; border-radius: 5px;
border-radius: 5px; margin: 0 auto;
margin: 0 auto;
}
}
&.folder {
align-items: flex-end;
} }
.folder-icon { .folder-icon {
align-items: flex-end;
@include font-size(80); @include font-size(80);
margin: 0 auto; margin: 0 auto;
@@ -18,7 +18,7 @@
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }" :class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
> >
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item" :class="data.type"> <div class="icon-item">
<!--If is file or image, then link item--> <!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ <span v-if="isFile" class="file-icon-text">{{
data.mimetype | limitCharacters data.mimetype | limitCharacters
@@ -28,15 +28,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--Image thumbnail--> <!--Image thumbnail-->
<img v-if="isImage" :src="data.thumbnail" :alt="data.name"/> <img v-if="isImage" class="image" :src="data.thumbnail" :alt="data.name"/>
<!--Else show only folder icon--> <!--Else show only folder icon-->
<FontAwesomeIcon <FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
v-if="isFolder"
:class="{'is-deleted': isDeleted}"
class="folder-icon"
icon="folder"
/>
</div> </div>
<!--Name--> <!--Name-->
@@ -50,7 +45,7 @@
>{{ itemName }}</span> >{{ itemName }}</span>
<!--Other attributes--> <!--Other attributes-->
<span v-if="isFile || isImage" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span> <span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<span v-if="isFolder" class="item-length"> <span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }} {{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
@@ -81,7 +76,7 @@
return this.data.type === 'folder' return this.data.type === 'folder'
}, },
isFile() { isFile() {
return this.data.type === 'file' return this.data.type !== 'folder' && this.data.type !== 'image'
}, },
isImage() { isImage() {
return this.data.type === 'image' return this.data.type === 'image'
@@ -248,7 +243,11 @@
.name { .name {
white-space: nowrap; white-space: nowrap;
//display: inline-block;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover { &[contenteditable='true']:hover {
text-decoration: underline; text-decoration: underline;
@@ -276,6 +275,7 @@
} }
.icon-item { .icon-item {
text-align: center;
position: relative; position: relative;
flex: 0 0 50px; flex: 0 0 50px;
line-height: 0; line-height: 0;
@@ -305,38 +305,32 @@
} }
} }
&.file { .file-icon-text {
line-height: 1;
top: 40%;
@include font-size(11);
margin: 0 auto;
position: absolute;
text-align: center; text-align: center;
left: 0;
.file-icon-text { right: 0;
line-height: 1; color: $theme;
top: 40%; font-weight: 600;
@include font-size(11); user-select: none;
margin: 0 auto; max-width: 50px;
position: absolute; max-height: 20px;
text-align: center; overflow: hidden;
left: 0; text-overflow: ellipsis;
right: 0; white-space: nowrap;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 50px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
&.image { .image {
img { object-fit: cover;
object-fit: cover; user-select: none;
user-select: none; max-width: 100%;
max-width: 100%; border-radius: 5px;
border-radius: 5px; width: 50px;
width: 50px; height: 50px;
height: 50px;
}
} }
} }
@@ -0,0 +1,60 @@
<template>
<div v-if="canBePreview" class="preview">
<img v-if="fileInfoDetail.type == 'image'" :src="fileInfoDetail.thumbnail" :alt="fileInfoDetail.name" />
<audio v-else-if="fileInfoDetail.type == 'audio'" :src="fileInfoDetail.file_url" controlsList="nodownload" controls></audio>
<video v-else-if="fileInfoDetail.type == 'video'" controlsList="nodownload" disablePictureInPicture playsinline controls>
<source :src="fileInfoDetail.file_url" type="video/mp4">
</video>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { includes } from 'lodash'
export default {
name: 'FilePreview',
computed: {
...mapGetters(['fileInfoDetail']),
canBePreview() {
return this.fileInfoDetail && ! includes([
'folder', 'file'
], this.fileInfoDetail.type)
}
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.preview {
width: 100%;
display: block;
margin-bottom: 7px;
img {
border-radius: 4px;
overflow: hidden;
width: 100%;
object-fit: cover;
}
audio {
width: 100%;
&::-webkit-media-controls-panel {
background-color: $light_background;
}
&::-webkit-media-controls-play-button {
color: $theme;
}
}
video {
width: 100%;
height: auto;
border-radius: 3px;
}
}
</style>
@@ -1,5 +1,10 @@
<template> <template>
<div class="file-content" :class="{ 'is-offset': uploadingFilesCount }"> <div class="file-content" :class="{ 'is-offset': uploadingFilesCount, 'is-dragging': isDragging }"
@dragover.prevent
@drop.stop.prevent="dropUpload($event)"
@dragover="dragEnter"
@dragleave="dragLeave"
>
<div <div
class="files-container" class="files-container"
ref="fileContainer" ref="fileContainer"
@@ -18,7 +23,10 @@
<MobileActions v-if="$isMinimalScale()" /> <MobileActions v-if="$isMinimalScale()" />
<!--Item previews list--> <!--Item previews list-->
<div v-if="isList" class="file-list-wrapper"> <div
v-if="isList"
class="file-list-wrapper"
>
<transition-group <transition-group
name="file" name="file"
tag="section" tag="section"
@@ -27,7 +35,7 @@
> >
<FileItemList <FileItemList
@dragstart="dragStart(item)" @dragstart="dragStart(item)"
@drop="dragFinish(item)" @drop.stop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)" @contextmenu.native.prevent="contextMenu($event, item)"
:data="item" :data="item"
v-for="item in data" v-for="item in data"
@@ -47,7 +55,7 @@
> >
<FileItemGrid <FileItemGrid
@dragstart="dragStart(item)" @dragstart="dragStart(item)"
@drop="dragFinish(item)" @drop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)" @contextmenu.native.prevent="contextMenu($event, item)"
:data="item" :data="item"
v-for="item in data" v-for="item in data"
@@ -133,10 +141,23 @@
}, },
data() { data() {
return { return {
draggingId: undefined draggingId: undefined,
isDragging: false,
} }
}, },
methods: { methods: {
dropUpload(event) {
// Upload external file
this.$uploadExternalFiles(event, this.currentFolder.unique_id)
this.isDragging = false
},
dragEnter() {
this.isDragging = true
},
dragLeave() {
this.isDragging = false
},
dragStart(data) { dragStart(data) {
events.$emit('dragstart', data) events.$emit('dragstart', data)
@@ -144,15 +165,26 @@
// Store dragged folder // Store dragged folder
this.draggingId = data this.draggingId = data
}, },
dragFinish(data) { dragFinish(data, event) {
// Prevent to drop on file or image
if (data.type !== 'folder' || this.draggingId === data) return
// Move folder to new parent if (event.dataTransfer.items.length == 0) {
this.moveTo(this.draggingId, data)
}, // Prevent to drop on file or image
moveTo(from_item, to_item) { if (data.type !== 'folder' || this.draggingId === data) return
this.$store.dispatch('moveItem', [from_item, to_item])
// Move folder to new parent
this.$store.dispatch('moveItem', [this.draggingId, data])
} else {
// Get unique_id of current folder
const unique_id = data.type !== 'folder' ? this.currentFolder.unique_id : data.unique_id
// Upload external file
this.$uploadExternalFiles(event, unique_id)
}
this.isDragging = false
}, },
contextMenu(event, item) { contextMenu(event, item) {
events.$emit('contextMenu:show', event, item) events.$emit('contextMenu:show', event, item)
@@ -225,6 +257,10 @@
.file-content { .file-content {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
&.is-dragging {
@include transform(scale(0.99));
}
} }
.files-container { .files-container {
@@ -11,7 +11,7 @@
<ul class="menu-options"> <ul class="menu-options">
<li class="menu-option" <li class="menu-option"
@click="addToFavourites" @click="addToFavourites"
v-if="! $isTrashLocation() && fileInfoDetail && fileInfoDetail.type === 'folder'" v-if="! $isTrashLocation() && fileInfoDetail && isFolder"
> >
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }} {{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li> </li>
@@ -39,7 +39,7 @@
<li <li
class="menu-option" class="menu-option"
@click="downloadItem" @click="downloadItem"
v-if="isFile || isImage" v-if="! isFolder"
> >
{{ $t('context_menu.download') }} {{ $t('context_menu.download') }}
</li> </li>
@@ -76,19 +76,13 @@
return this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id) return this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)
}, },
isFile() { isFile() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'file' return (this.fileInfoDetail && this.fileInfoDetail.type !== 'folder') && (this.fileInfoDetail && this.fileInfoDetail.type !== 'image')
? true
: false
}, },
isImage() { isImage() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'image' return this.fileInfoDetail && this.fileInfoDetail.type === 'image'
? true
: false
}, },
isFolder() { isFolder() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'folder' return this.fileInfoDetail && this.fileInfoDetail.type === 'folder'
? true
: false
} }
}, },
data() { data() {
@@ -2,7 +2,7 @@
<div class="file-item"> <div class="file-item">
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item" :class="file.type"> <div class="icon-item">
<!--If is file or image, then link item--> <!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span> <span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
@@ -11,7 +11,7 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--Image thumbnail--> <!--Image thumbnail-->
<img v-if="isImage" :src="file.thumbnail" :alt="file.name"/> <img v-if="isImage" class="image" :src="file.thumbnail" :alt="file.name"/>
<!--Else show only folder icon--> <!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder"/> <FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder"/>
@@ -41,7 +41,7 @@
return this.file.type === 'folder' return this.file.type === 'folder'
}, },
isFile() { isFile() {
return this.file.type === 'file' return this.file.type !== 'folder' && this.file.type !== 'image'
}, },
isImage() { isImage() {
return this.file.type === 'image' return this.file.type === 'image'
@@ -87,6 +87,8 @@
.icon-item { .icon-item {
position: relative; position: relative;
min-width: 40px; min-width: 40px;
text-align: center;
line-height: 0;
.file-icon { .file-icon {
@include font-size(35); @include font-size(35);
@@ -106,39 +108,32 @@
} }
} }
&.file { .file-icon-text {
line-height: 1;
top: 40%;
@include font-size(9);
margin: 0 auto;
position: absolute;
text-align: center; text-align: center;
left: 0;
.file-icon-text { right: 0;
top: 40%; color: $theme;
@include font-size(9); font-weight: 600;
margin: 0 auto; user-select: none;
position: absolute; max-width: 20px;
text-align: center; max-height: 20px;
left: 0; overflow: hidden;
right: 0; text-overflow: ellipsis;
color: $theme; white-space: nowrap;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
&.image { .image {
line-height: 0; object-fit: cover;
user-select: none;
img { max-width: 100%;
object-fit: cover; border-radius: 5px;
user-select: none; width: 36px;
max-width: 100%; height: 36px;
border-radius: 5px;
width: 36px;
height: 36px;
}
} }
} }
} }
@@ -2,7 +2,7 @@
<div class="file-item"> <div class="file-item">
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item" :class="file.type"> <div class="icon-item">
<!--If is file or image, then link item--> <!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span> <span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
@@ -11,10 +11,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" /> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" />
<!--Image thumbnail--> <!--Image thumbnail-->
<img v-if="isImage" :src="file.thumbnail" :alt="file.name" /> <img v-if="isImage" class="image" :src="file.thumbnail" :alt="file.name" />
<!--Else show only folder icon--> <!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" /> <FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" />
</div> </div>
<!--Name--> <!--Name-->
@@ -24,7 +24,7 @@
<span class="name" >{{ file.name }}</span> <span class="name" >{{ file.name }}</span>
<!--Other attributes--> <!--Other attributes-->
<span v-if="isFile || isImage" class="item-size">{{ file.filesize }}, {{ file.created_at }}</span> <span v-if="! isFolder" class="item-size">{{ file.filesize }}, {{ file.created_at }}</span>
<span v-if="isFolder" class="item-length">{{ file.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ file.created_at }}</span> <span v-if="isFolder" class="item-length">{{ file.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ file.created_at }}</span>
</div> </div>
@@ -41,7 +41,7 @@ export default {
return this.file.type === 'folder' return this.file.type === 'folder'
}, },
isFile() { isFile() {
return this.file.type === 'file' return this.file.type !== 'folder' && this.file.type !== 'image'
}, },
isImage() { isImage() {
return this.file.type === 'image' return this.file.type === 'image'
@@ -101,6 +101,8 @@ export default {
.icon-item { .icon-item {
position: relative; position: relative;
min-width: 40px; min-width: 40px;
text-align: center;
line-height: 0;
.file-icon { .file-icon {
@include font-size(35); @include font-size(35);
@@ -112,39 +114,32 @@ export default {
} }
} }
&.file { .file-icon-text {
top: 40%;
@include font-size(9);
line-height: 1;
margin: 0 auto;
position: absolute;
text-align: center; text-align: center;
left: 0;
.file-icon-text { right: 0;
top: 40%; color: $theme;
@include font-size(9); font-weight: 600;
margin: 0 auto; user-select: none;
position: absolute; max-width: 20px;
text-align: center; max-height: 20px;
left: 0; overflow: hidden;
right: 0; text-overflow: ellipsis;
color: $theme; white-space: nowrap;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
&.image { .image {
line-height: 0; object-fit: cover;
user-select: none;
img { max-width: 100%;
object-fit: cover; border-radius: 5px;
user-select: none; width: 36px;
max-width: 100%; height: 36px;
border-radius: 5px;
width: 36px;
height: 36px;
}
} }
} }
} }
+73
View File
@@ -130,6 +130,79 @@ const Helpers = {
} }
} }
Vue.prototype.$uploadExternalFiles = async function(event, parent_id) {
// Prevent submit empty files
if (event.dataTransfer.items.length == 0) return
// Get files
const files = [...event.dataTransfer.items].map(item => item.getAsFile());
if (this.$store.getters.app.storage.percentage >= 100) {
events.$emit('alert:open', {
emoji: '😬😬😬',
title: this.$t('popup_exceed_limit.title'),
message: this.$t('popup_exceed_limit.message')
})
return
}
let fileCountSucceed = 1
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: files.length
})
for (var i = files.length - 1; i >= 0; i--) {
let formData = new FormData()
// Append data
formData.append('file', files[i])
// Append form data
formData.append('parent_id', parent_id)
// Upload data
await store.dispatch('uploadFiles', formData).then(() => {
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: files.length
})
// Progress file log
store.commit('INCREASE_FOLDER_ITEM', parent_id)
// Uploading finished
if (files.length === fileCountSucceed) {
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
} else {
// Add uploaded file
fileCountSucceed++
}
}).catch(error => {
if (error.response.status == 423) {
events.$emit('alert:open', {
emoji: '😬😬😬',
title: this.$t('popup_exceed_limit.title'),
message: this.$t('popup_exceed_limit.message')
})
} else {
// Show error message
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
}
})
}
}
Vue.prototype.$downloadFile = function(url, filename) { Vue.prototype.$downloadFile = function(url, filename) {
var anchor = document.createElement('a') var anchor = document.createElement('a')
+4
View File
@@ -8,6 +8,8 @@ import Helpers from './helpers'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { import {
faFileAudio,
faFileVideo,
faSyncAlt, faSyncAlt,
faShare, faShare,
faHome, faHome,
@@ -34,6 +36,8 @@ import {
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
faFileAudio,
faFileVideo,
faHdd, faHdd,
faSyncAlt, faSyncAlt,
faShare, faShare,
-19
View File
@@ -1,19 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];
-19
View File
@@ -1,19 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Predchádzajúca',
'next' => 'Nasledujúca &raquo;',
];
-6
View File
@@ -23,10 +23,4 @@ mix.js('resources/js/main.js', 'public/js')
} }
}, },
}) })
.options({
hmrOptions: {
host: '192.168.1.131',
port: '8080'
},
})
.disableNotifications(); .disableNotifications();