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

View File

@@ -428,7 +428,7 @@ class FileManagerController extends Controller
// File
$filename = Str::random() . '-' . str_replace(' ', '', $file->getClientOriginalName());
$filetype = 'file';
$filetype = get_file_type($file);
$thumbnail = null;
$filesize = $file->getSize();
$directory = 'file-manager';
@@ -442,9 +442,8 @@ class FileManagerController extends Controller
Storage::disk('local')->putFileAs($directory, $file, $filename, 'public');
// Create image thumbnail
if (substr($file->getMimeType(), 0, 5) == 'image') {
if ( $filetype == 'image' ) {
$filetype = 'image';
$thumbnail = 'thumbnail-' . $filename;
// Create intervention image
@@ -562,6 +561,8 @@ class FileManagerController extends Controller
$response->header("Content-Type", $type);
$response->header("Content-Disposition", 'attachment; filename=' . $filename);
$response->header("Content-Length", $size);
$response->header("Accept-Ranges", "bytes");
$response->header("Content-Range", "bytes 0-" . $size . "/" . $size);
return $response;
}

View File

@@ -100,7 +100,8 @@ function get_storage_fill_percentage($used, $capacity)
*
* @return string
*/
function user_storage_percentage() {
function user_storage_percentage()
{
$user = \Illuminate\Support\Facades\Auth::user();
@@ -171,4 +172,30 @@ function format_date($date, $format = '%d. %B. %Y, %H:%M')
$start = Carbon::parse($date);
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';
}
}

View File

@@ -10,6 +10,7 @@
"require": {
"php": "^7.2",
"askedio/laravel-soft-cascade": "^6.0",
"doctrine/dbal": "^2.10",
"fideloper/proxy": "^4.0",
"fruitcake/laravel-cors": "^1.0",
"gabrielelana/byte-units": "^0.5.0",

660
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -7,7 +7,7 @@
#
# Host: 127.0.0.1 (MySQL 5.7.25)
# 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,
`mimetype` 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,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
@@ -100,7 +100,7 @@ DROP TABLE IF EXISTS `migrations`;
CREATE TABLE `migrations` (
`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,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -122,7 +122,8 @@ VALUES
(10,'2019_08_19_000000_create_failed_jobs_table',1),
(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),
(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 */;
UNLOCK TABLES;
@@ -137,7 +138,7 @@ CREATE TABLE `oauth_access_tokens` (
`id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint(20) unsigned DEFAULT 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,
`revoked` tinyint(1) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
@@ -175,7 +176,7 @@ DROP TABLE IF EXISTS `oauth_clients`;
CREATE TABLE `oauth_clients` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`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,
`redirect` text COLLATE utf8mb4_unicode_ci NOT NULL,
`personal_access_client` tinyint(1) NOT NULL,
@@ -234,8 +235,8 @@ CREATE TABLE `oauth_refresh_tokens` (
DROP TABLE IF EXISTS `password_resets`;
CREATE TABLE `password_resets` (
`email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
KEY `password_resets_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

1070
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,14 +12,14 @@
"devDependencies": {
"axios": "^0.19",
"cross-env": "^5.1",
"laravel-mix": "^5.0.1",
"laravel-mix": "^5.0.4",
"resolve-url-loader": "^2.3.1",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"css-element-queries": "^1.2.3",
"lodash": "^4.17.15",

View File

@@ -5,6 +5,13 @@
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
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Binary file not shown.

2
public/css/app.css vendored
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);

2
public/js/main.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,40 +1,4 @@
{
"/js/main.js": "/js/main.js",
"/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"
"/css/app.css": "/css/app.css"
}

View File

@@ -88,7 +88,6 @@
<style lang="scss">
@import "@assets/app.scss";
#files-view {
font-family: 'Nunito', sans-serif;
font-size: 16px;
@@ -161,27 +160,21 @@
margin-bottom: 10px;
height: 90px;
&.file {
.file-icon {
@include font-size(75);
}
.file-icon-text {
@include font-size(12);
}
.file-icon {
@include font-size(75);
}
&.folder {
@include font-size(14);
.folder-icon {
margin-top: 0;
margin-bottom: 0;
}
.file-icon-text {
@include font-size(12);
}
&.image img {
.folder-icon {
@include font-size(75);
margin-top: 0;
margin-bottom: 0;
}
.image {
width: 90px;
height: 90px;
}

View File

@@ -8,7 +8,7 @@
<ul class="menu-options" id="menu-options-list" ref="list" @click="closeAndResetContextMenu">
<!--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>
<!--Edits-->
@@ -21,7 +21,7 @@
<!--Others-->
<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>
</div>
</template>
@@ -34,11 +34,14 @@
name: 'ContextMenu',
computed: {
...mapGetters(['app']),
isFolder() {
return this.item && this.item.type === 'folder'
},
isFile() {
return this.item && this.item.type === 'file' ? true : false
return (this.item && this.item.type !== 'folder') && (this.item && this.item.type !== 'image')
},
isImage() {
return this.item && this.item.type === 'image' ? true : false
return this.item && this.item.type === 'image'
},
isInFavourites() {
return this.app.favourites.find(el => el.unique_id == this.item.unique_id)

View File

@@ -1,31 +1,18 @@
<template>
<div v-if="fileInfoDetail">
<div class="file-headline" spellcheck="false">
<!--Image thumbnail-->
<div v-if="fileInfoDetail.type == 'image'" class="image-preview">
<img
@dblclick="$openImageOnNewTab(fileInfoDetail.file_url)"
:src="fileInfoDetail.thumbnail"
:alt="fileInfoDetail.name"
/>
</div>
<FilePreview />
<!--File info-->
<div class="flex">
<div class="icon">
<div class="icon-preview" @dblclick="getItemAction">
<FontAwesomeIcon
v-if="fileInfoDetail.type == 'folder'"
icon="folder"
></FontAwesomeIcon>
<FontAwesomeIcon
v-if="fileInfoDetail.type == 'file'"
icon="file"
></FontAwesomeIcon>
<FontAwesomeIcon
v-if="fileInfoDetail.type == 'image'"
icon="file-image"
></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'folder'" icon="folder"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'file'" icon="file"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'image'" icon="file-image"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'video'" icon="file-video"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'audio'" icon="file-audio"></FontAwesomeIcon>
</div>
</div>
<div class="file-info">
@@ -64,12 +51,16 @@
</template>
<script>
import FilePreview from '@/components/VueFileManagerComponents/FilesView/FilePreview'
import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from "@/bus"
export default {
name: 'FilesInfoPanel',
name: 'FileInfoPanel',
components: {
FilePreview
},
computed: {
...mapGetters(['fileInfoDetail'])
},
@@ -122,20 +113,6 @@
margin-bottom: 20px;
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 {
display: flex;
align-items: top;
@@ -251,6 +228,13 @@
span {
color: $dark_mode_text_primary
}
.action-button {
.icon {
color: $dark_mode_text_primary;
}
}
}
}
}

View File

@@ -19,7 +19,7 @@
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
>
<!--Thumbnail for item-->
<div class="icon-item" :class="data.type">
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{
data.mimetype
@@ -29,15 +29,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--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-->
<FontAwesomeIcon
v-if="isFolder"
:class="{'is-deleted': isDeleted}"
class="folder-icon"
icon="folder"
/>
<FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
</div>
<!--Name-->
@@ -52,7 +47,7 @@
>
<!--Other attributes-->
<span v-if="isFile || isImage" class="item-size">{{
<span v-if="! isFolder" class="item-size">{{
data.filesize
}}</span>
@@ -82,7 +77,7 @@
return this.data.type === 'folder'
},
isFile() {
return this.data.type === 'file'
return this.data.type !== 'folder' && this.data.type !== 'image'
},
isImage() {
return this.data.type === 'image'
@@ -207,7 +202,6 @@
}
}
.file-wrapper {
position: relative;
text-align: center;
@@ -232,6 +226,11 @@
.name {
display: block;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover {
text-decoration: underline;
}
@@ -282,6 +281,7 @@
}
.icon-item {
text-align: center;
position: relative;
height: 110px;
margin-bottom: 20px;
@@ -303,41 +303,33 @@
}
}
&.file {
.file-icon-text {
margin: 5px auto 0;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 65px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-icon-text {
margin: 5px auto 0;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 65px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.image {
img {
max-width: 95%;
object-fit: cover;
user-select: none;
height: 110px;
border-radius: 5px;
margin: 0 auto;
}
}
&.folder {
align-items: flex-end;
.image {
max-width: 95%;
object-fit: cover;
user-select: none;
height: 110px;
border-radius: 5px;
margin: 0 auto;
}
.folder-icon {
align-items: flex-end;
@include font-size(80);
margin: 0 auto;

View File

@@ -18,7 +18,7 @@
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
>
<!--Thumbnail for item-->
<div class="icon-item" :class="data.type">
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{
data.mimetype | limitCharacters
@@ -28,15 +28,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--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-->
<FontAwesomeIcon
v-if="isFolder"
:class="{'is-deleted': isDeleted}"
class="folder-icon"
icon="folder"
/>
<FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
</div>
<!--Name-->
@@ -50,7 +45,7 @@
>{{ itemName }}</span>
<!--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">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
@@ -81,7 +76,7 @@
return this.data.type === 'folder'
},
isFile() {
return this.data.type === 'file'
return this.data.type !== 'folder' && this.data.type !== 'image'
},
isImage() {
return this.data.type === 'image'
@@ -248,7 +243,11 @@
.name {
white-space: nowrap;
//display: inline-block;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover {
text-decoration: underline;
@@ -276,6 +275,7 @@
}
.icon-item {
text-align: center;
position: relative;
flex: 0 0 50px;
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;
.file-icon-text {
line-height: 1;
top: 40%;
@include font-size(11);
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 50px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 50px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.image {
img {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 50px;
height: 50px;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 50px;
height: 50px;
}
}

View File

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

View File

@@ -1,5 +1,10 @@
<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
class="files-container"
ref="fileContainer"
@@ -18,7 +23,10 @@
<MobileActions v-if="$isMinimalScale()" />
<!--Item previews list-->
<div v-if="isList" class="file-list-wrapper">
<div
v-if="isList"
class="file-list-wrapper"
>
<transition-group
name="file"
tag="section"
@@ -27,7 +35,7 @@
>
<FileItemList
@dragstart="dragStart(item)"
@drop="dragFinish(item)"
@drop.stop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:data="item"
v-for="item in data"
@@ -47,7 +55,7 @@
>
<FileItemGrid
@dragstart="dragStart(item)"
@drop="dragFinish(item)"
@drop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:data="item"
v-for="item in data"
@@ -133,10 +141,23 @@
},
data() {
return {
draggingId: undefined
draggingId: undefined,
isDragging: false,
}
},
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) {
events.$emit('dragstart', data)
@@ -144,15 +165,26 @@
// Store dragged folder
this.draggingId = data
},
dragFinish(data) {
// Prevent to drop on file or image
if (data.type !== 'folder' || this.draggingId === data) return
dragFinish(data, event) {
// Move folder to new parent
this.moveTo(this.draggingId, data)
},
moveTo(from_item, to_item) {
this.$store.dispatch('moveItem', [from_item, to_item])
if (event.dataTransfer.items.length == 0) {
// Prevent to drop on file or image
if (data.type !== 'folder' || this.draggingId === data) return
// 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) {
events.$emit('contextMenu:show', event, item)
@@ -225,6 +257,10 @@
.file-content {
display: flex;
flex-wrap: nowrap;
&.is-dragging {
@include transform(scale(0.99));
}
}
.files-container {

View File

@@ -11,7 +11,7 @@
<ul class="menu-options">
<li class="menu-option"
@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') }}
</li>
@@ -39,7 +39,7 @@
<li
class="menu-option"
@click="downloadItem"
v-if="isFile || isImage"
v-if="! isFolder"
>
{{ $t('context_menu.download') }}
</li>
@@ -76,19 +76,13 @@
return this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)
},
isFile() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'file'
? true
: false
return (this.fileInfoDetail && this.fileInfoDetail.type !== 'folder') && (this.fileInfoDetail && this.fileInfoDetail.type !== 'image')
},
isImage() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'image'
? true
: false
},
isFolder() {
return this.fileInfoDetail && this.fileInfoDetail.type === 'folder'
? true
: false
}
},
data() {

View File

@@ -2,7 +2,7 @@
<div class="file-item">
<!--Thumbnail for item-->
<div class="icon-item" :class="file.type">
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
@@ -11,7 +11,7 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<!--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-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder"/>
@@ -41,7 +41,7 @@
return this.file.type === 'folder'
},
isFile() {
return this.file.type === 'file'
return this.file.type !== 'folder' && this.file.type !== 'image'
},
isImage() {
return this.file.type === 'image'
@@ -87,6 +87,8 @@
.icon-item {
position: relative;
min-width: 40px;
text-align: center;
line-height: 0;
.file-icon {
@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;
.file-icon-text {
top: 40%;
@include font-size(9);
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.image {
line-height: 0;
img {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
}
}

View File

@@ -2,7 +2,7 @@
<div class="file-item">
<!--Thumbnail for item-->
<div class="icon-item" :class="file.type">
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
@@ -11,10 +11,10 @@
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" />
<!--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-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" />
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" />
</div>
<!--Name-->
@@ -24,7 +24,7 @@
<span class="name" >{{ file.name }}</span>
<!--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>
</div>
@@ -41,7 +41,7 @@ export default {
return this.file.type === 'folder'
},
isFile() {
return this.file.type === 'file'
return this.file.type !== 'folder' && this.file.type !== 'image'
},
isImage() {
return this.file.type === 'image'
@@ -101,6 +101,8 @@ export default {
.icon-item {
position: relative;
min-width: 40px;
text-align: center;
line-height: 0;
.file-icon {
@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;
.file-icon-text {
top: 40%;
@include font-size(9);
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.image {
line-height: 0;
img {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
}
}

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) {
var anchor = document.createElement('a')

View File

@@ -8,6 +8,8 @@ import Helpers from './helpers'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import {
faFileAudio,
faFileVideo,
faSyncAlt,
faShare,
faHome,
@@ -34,6 +36,8 @@ import {
} from '@fortawesome/free-solid-svg-icons'
library.add(
faFileAudio,
faFileVideo,
faHdd,
faSyncAlt,
faShare,

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

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
webpack.mix.js vendored
View File

@@ -23,10 +23,4 @@ mix.js('resources/js/main.js', 'public/js')
}
},
})
.options({
hmrOptions: {
host: '192.168.1.131',
port: '8080'
},
})
.disableNotifications();