vue frontend update

This commit is contained in:
carodej
2020-04-17 11:33:06 +02:00
parent ae4353cc4b
commit 506c39896a
45 changed files with 2366 additions and 784 deletions
+1 -2
View File
@@ -9,11 +9,10 @@ use Illuminate\Support\Str;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use TeamTNT\TNTSearch\Indexer\TNTIndexer; use TeamTNT\TNTSearch\Indexer\TNTIndexer;
use \Illuminate\Database\Eloquent\SoftDeletes; use \Illuminate\Database\Eloquent\SoftDeletes;
use \Askedio\SoftCascade\Traits\SoftCascadeTrait;
class FileManagerFile extends Model class FileManagerFile extends Model
{ {
use Searchable, SoftDeletes, SoftCascadeTrait; use Searchable, SoftDeletes;
protected $guarded = [ protected $guarded = [
+14 -7
View File
@@ -11,20 +11,15 @@ use RecursiveArrayIterator;
use RecursiveIteratorIterator; use RecursiveIteratorIterator;
use TeamTNT\TNTSearch\Indexer\TNTIndexer; use TeamTNT\TNTSearch\Indexer\TNTIndexer;
use \Illuminate\Database\Eloquent\SoftDeletes; use \Illuminate\Database\Eloquent\SoftDeletes;
use \Askedio\SoftCascade\Traits\SoftCascadeTrait;
class FileManagerFolder extends Model class FileManagerFolder extends Model
{ {
use Searchable, SoftDeletes, SoftCascadeTrait; use Searchable, SoftDeletes;
protected $guarded = [ protected $guarded = [
'id' 'id'
]; ];
protected $softCascade = [
'children', 'files'
];
protected $appends = [ protected $appends = [
'items', 'trashed_items' 'items', 'trashed_items'
]; ];
@@ -116,7 +111,6 @@ class FileManagerFolder extends Model
*/ */
public function files() public function files()
{ {
return $this->hasMany('App\FileManagerFile', 'folder_id', 'unique_id'); return $this->hasMany('App\FileManagerFile', 'folder_id', 'unique_id');
} }
@@ -178,9 +172,22 @@ class FileManagerFolder extends Model
static::deleting(function ($item) { static::deleting(function ($item) {
if ( $item->isForceDeleting() ) {
$item->trashed_children()->each(function($folder) {
$folder->forceDelete();
});
} else {
$item->children()->each(function($folder) { $item->children()->each(function($folder) {
$folder->delete(); $folder->delete();
}); });
$item->files()->each(function($file) {
$file->delete();
});
}
}); });
static::restoring(function ($item) { static::restoring(function ($item) {
+53 -74
View File
@@ -116,8 +116,12 @@ class FileManagerController extends Controller
$user_id = Auth::id(); $user_id = Auth::id();
// Search files id db // Search files id db
$searched_files = FileManagerFile::search($request->input('query'))->where('user_id', $user_id)->get(); $searched_files = FileManagerFile::search($request->input('query'))
$searched_folders = FileManagerFolder::search($request->input('query'))->where('user_id', $user_id)->get(); ->where('user_id', $user_id)
->get();
$searched_folders = FileManagerFolder::search($request->input('query'))
->where('user_id', $user_id)
->get();
// Collect folders and files to single array // Collect folders and files to single array
return collect([$searched_folders, $searched_files])->collapse(); return collect([$searched_folders, $searched_files])->collapse();
@@ -225,19 +229,28 @@ class FileManagerController extends Controller
// Delete folder // Delete folder
if ($request->type === 'folder') { if ($request->type === 'folder') {
$item = FileManagerFolder::withTrashed() // Get folder
->with('folders') $folder = FileManagerFolder::withTrashed()
->with(['folders'])
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('unique_id', $request->unique_id) ->where('unique_id', $request->unique_id)
->first(); ->first();
// Remove folder from user favourites // Force delete children files
$user->favourites()->detach($request->unique_id);
foreach ($item->files as $file) {
if ($request->force_delete) { if ($request->force_delete) {
// Get children folder ids
$child_folders = filter_folders_ids($folder->trashed_folders, 'unique_id');
// Get children files
$files = FileManagerFile::onlyTrashed()
->where('user_id', $user->id)
->whereIn('folder_id', Arr::flatten([$request->unique_id, $child_folders]))
->get();
// Remove all children files
foreach ($files as $file) {
// Delete file // Delete file
Storage::disk('local')->delete('/file-manager/' . $file->basename); Storage::disk('local')->delete('/file-manager/' . $file->basename);
@@ -246,26 +259,22 @@ class FileManagerController extends Controller
// Delete file permanently // Delete file permanently
$file->forceDelete(); $file->forceDelete();
}
// Delete folder record
$folder->forceDelete();
} else { } else {
// Delete file from visibility // Remove folder from user favourites
$file->delete(); $user->favourites()->detach($request->unique_id);
}
}
// Delete record // Soft delete folder record
if ($request->force_delete) { $folder->delete();
}
$item->forceDelete();
} else { } else {
$item->delete(); $file = FileManagerFile::withTrashed()
}
}
if ($request->type === 'file' || $request->type === 'image') {
$item = FileManagerFile::withTrashed()
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('unique_id', $request->unique_id) ->where('unique_id', $request->unique_id)
->first(); ->first();
@@ -273,17 +282,17 @@ class FileManagerController extends Controller
if ($request->force_delete) { if ($request->force_delete) {
// Delete file // Delete file
Storage::disk('local')->delete('/file-manager/' . $item->basename); Storage::disk('local')->delete('/file-manager/' . $file->basename);
// Delete thumbnail if exist // Delete thumbnail if exist
if (!is_null($item->thumbnail)) Storage::disk('local')->delete('/file-manager/' . $item->thumbnail); if ($file->thumbnail) Storage::disk('local')->delete('/file-manager/' . $file->getOriginal('thumbnail'));
// Delete file permanently // Delete file permanently
$item->forceDelete(); $file->forceDelete();
} else { } else {
// Delete file from visibility // Soft delete file
$item->delete(); $file->delete();
} }
} }
} }
@@ -302,9 +311,21 @@ class FileManagerController extends Controller
$folders = FileManagerFolder::onlyTrashed()->where('user_id', $user_id)->get(); $folders = FileManagerFolder::onlyTrashed()->where('user_id', $user_id)->get();
$files = FileManagerFile::onlyTrashed()->where('user_id', $user_id)->get(); $files = FileManagerFile::onlyTrashed()->where('user_id', $user_id)->get();
// Force delete every item // Force delete folder
$folders->each->forceDelete(); $folders->each->forceDelete();
$files->each->forceDelete();
// Force delete files
foreach ($files as $file) {
// Delete file
Storage::disk('local')->delete('/file-manager/' . $file->basename);
// Delete thumbnail if exist
if ($file->thumbnail) Storage::disk('local')->delete('/file-manager/' . $file->getOriginal('thumbnail'));
// Delete file permanently
$file->forceDelete();
}
// Return response // Return response
return response('Done!', 200); return response('Done!', 200);
@@ -341,10 +362,7 @@ class FileManagerController extends Controller
$item->parent_id = 0; $item->parent_id = 0;
$item->save(); $item->save();
} }
} } else {
// Get file
if ($request->type === 'file' || $request->type === 'image') {
// Get item // Get item
$item = FileManagerFile::onlyTrashed()->where('user_id', $user_id)->where('unique_id', $request->unique_id)->first(); $item = FileManagerFile::onlyTrashed()->where('user_id', $user_id)->where('unique_id', $request->unique_id)->first();
@@ -360,45 +378,6 @@ class FileManagerController extends Controller
$item->restore(); $item->restore();
} }
/**
* Delete Item
*
* @param Request $request
*/
public function delete_items(Request $request)
{
// Validate request
$validator = Validator::make($request->all(), [
'items' => 'required|json',
]);
// Return error
if ($validator->fails()) abort(400, 'Bad input');
foreach ($request->input('items') as $file) {
if ($file['type'] === 'file' || $file['type'] === 'image') {
$item = FileManagerFile::where('unique_id', $file['unique_id'])->first();
} else {
$item = FileManagerFolder::where('unique_id', $file['unique_id'])->first();
}
// Delete file
Storage::disk('local')->delete('/file-manager/' . $item->basename);
// Delete thumbnail if exist
if (!is_null($item->thumbnail)) {
Storage::disk('local')->delete('/file-manager/' . $item->thumbnail);
}
// Permanently delete file
$item->forceDelete();
}
}
/** /**
* Upload items * Upload items
* *
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class FileSharingController extends Controller
{
/**
* Generate file share link
*
* @param Request $request
* @return array
*/
public function generate_link(Request $request) {
return 'http://192.168.1.131:8000/shared?token=' . Str::random(64);
}
/**
* Check Password for protected item
*
* @param Request $request
* @return array
*/
public function check_password(Request $request) {
return $request->all();
}
}
-1
View File
@@ -9,7 +9,6 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"askedio/laravel-soft-cascade": "^6.0",
"doctrine/dbal": "^2.10", "doctrine/dbal": "^2.10",
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.0",
"fruitcake/laravel-cors": "^1.0", "fruitcake/laravel-cors": "^1.0",
+156 -213
View File
@@ -1,217 +1,160 @@
{ {
"/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.b11d3c337dcbe36de92d.hot-update.js": "/js/main.b11d3c337dcbe36de92d.hot-update.js", "/js/main.45b2667847dcbcbacd6c.hot-update.js": "/js/main.45b2667847dcbcbacd6c.hot-update.js",
"/js/main.30de75440af7c882f1a6.hot-update.js": "/js/main.30de75440af7c882f1a6.hot-update.js", "/js/main.62c5cc2efcbf0d1f3d41.hot-update.js": "/js/main.62c5cc2efcbf0d1f3d41.hot-update.js",
"/js/main.dd76ca2f4f2e44b89e28.hot-update.js": "/js/main.dd76ca2f4f2e44b89e28.hot-update.js", "/js/main.f5178d917441e5670da1.hot-update.js": "/js/main.f5178d917441e5670da1.hot-update.js",
"/js/main.efe95c75e3dc04fda73e.hot-update.js": "/js/main.efe95c75e3dc04fda73e.hot-update.js", "/js/main.e19949b4ab90da976bbe.hot-update.js": "/js/main.e19949b4ab90da976bbe.hot-update.js",
"/js/main.b2921e5ff3050aa390ce.hot-update.js": "/js/main.b2921e5ff3050aa390ce.hot-update.js", "/js/main.960ccae31fd3ed497280.hot-update.js": "/js/main.960ccae31fd3ed497280.hot-update.js",
"/js/main.a4123c79b197780e3ade.hot-update.js": "/js/main.a4123c79b197780e3ade.hot-update.js", "/js/main.406ceecb66439f3f666b.hot-update.js": "/js/main.406ceecb66439f3f666b.hot-update.js",
"/js/main.03410e4c21da6f93734b.hot-update.js": "/js/main.03410e4c21da6f93734b.hot-update.js", "/js/main.a2fcc113dfe5c7f6e92e.hot-update.js": "/js/main.a2fcc113dfe5c7f6e92e.hot-update.js",
"/js/main.1593b10ba86b3c716d71.hot-update.js": "/js/main.1593b10ba86b3c716d71.hot-update.js", "/js/main.9ca13a12526522d88362.hot-update.js": "/js/main.9ca13a12526522d88362.hot-update.js",
"/js/main.22d8df3e92baf3fce90e.hot-update.js": "/js/main.22d8df3e92baf3fce90e.hot-update.js", "/js/main.c229b6a816917bebb5b5.hot-update.js": "/js/main.c229b6a816917bebb5b5.hot-update.js",
"/js/main.ac0bf715cad6262cd972.hot-update.js": "/js/main.ac0bf715cad6262cd972.hot-update.js", "/js/main.21c162035729fed110ca.hot-update.js": "/js/main.21c162035729fed110ca.hot-update.js",
"/js/main.c80c1dc635621381471d.hot-update.js": "/js/main.c80c1dc635621381471d.hot-update.js", "/js/main.40a17d8faff3f17c1c92.hot-update.js": "/js/main.40a17d8faff3f17c1c92.hot-update.js",
"/js/main.da6ee16f2dc02b63b0a4.hot-update.js": "/js/main.da6ee16f2dc02b63b0a4.hot-update.js", "/js/main.ed521bbc6a64cb55c015.hot-update.js": "/js/main.ed521bbc6a64cb55c015.hot-update.js",
"/js/main.cf7b74318c538b7712f4.hot-update.js": "/js/main.cf7b74318c538b7712f4.hot-update.js", "/js/main.532b35d72123575de690.hot-update.js": "/js/main.532b35d72123575de690.hot-update.js",
"/js/main.fd2d817f27f919390b48.hot-update.js": "/js/main.fd2d817f27f919390b48.hot-update.js", "/js/main.0dbc01ade0f34310012e.hot-update.js": "/js/main.0dbc01ade0f34310012e.hot-update.js",
"/js/main.1c8848af67829d4713b3.hot-update.js": "/js/main.1c8848af67829d4713b3.hot-update.js", "/js/main.d99afa857d33da6397cf.hot-update.js": "/js/main.d99afa857d33da6397cf.hot-update.js",
"/js/main.0fa0f809d8ddc549f097.hot-update.js": "/js/main.0fa0f809d8ddc549f097.hot-update.js", "/js/main.6099c72b40714248291c.hot-update.js": "/js/main.6099c72b40714248291c.hot-update.js",
"/js/main.e2f2102015c35aef9578.hot-update.js": "/js/main.e2f2102015c35aef9578.hot-update.js", "/js/main.56e79dc3fd40e683684d.hot-update.js": "/js/main.56e79dc3fd40e683684d.hot-update.js",
"/js/main.a8d912e21dda9b5e0a0a.hot-update.js": "/js/main.a8d912e21dda9b5e0a0a.hot-update.js", "/js/main.b5562439014533bf3ff2.hot-update.js": "/js/main.b5562439014533bf3ff2.hot-update.js",
"/js/main.12b8cef7186b82cd87ae.hot-update.js": "/js/main.12b8cef7186b82cd87ae.hot-update.js", "/js/main.78d46ab68efecb60d70c.hot-update.js": "/js/main.78d46ab68efecb60d70c.hot-update.js",
"/js/main.1a811cd94065c796f08c.hot-update.js": "/js/main.1a811cd94065c796f08c.hot-update.js", "/js/main.039aafe71e0927391a9c.hot-update.js": "/js/main.039aafe71e0927391a9c.hot-update.js",
"/js/main.aaea667ba4e9ff4889c5.hot-update.js": "/js/main.aaea667ba4e9ff4889c5.hot-update.js", "/js/main.ef8fe842f7680c572fab.hot-update.js": "/js/main.ef8fe842f7680c572fab.hot-update.js",
"/js/main.e1c1ec69ee1f0d2b7611.hot-update.js": "/js/main.e1c1ec69ee1f0d2b7611.hot-update.js", "/js/main.1fdf649d3ccbdce05837.hot-update.js": "/js/main.1fdf649d3ccbdce05837.hot-update.js",
"/js/main.0b0dd15fedb075bc0d00.hot-update.js": "/js/main.0b0dd15fedb075bc0d00.hot-update.js", "/js/main.e7c7d96caa245e1bb123.hot-update.js": "/js/main.e7c7d96caa245e1bb123.hot-update.js",
"/js/main.549d8172379f7c6ab028.hot-update.js": "/js/main.549d8172379f7c6ab028.hot-update.js", "/js/main.6bc39b87ec372cfa3dc8.hot-update.js": "/js/main.6bc39b87ec372cfa3dc8.hot-update.js",
"/js/main.7792632bf76683ef4588.hot-update.js": "/js/main.7792632bf76683ef4588.hot-update.js", "/js/main.ebe195c44f56007eef1d.hot-update.js": "/js/main.ebe195c44f56007eef1d.hot-update.js",
"/js/main.86940367851defcb1bac.hot-update.js": "/js/main.86940367851defcb1bac.hot-update.js", "/js/main.f37be265024e0905fdb1.hot-update.js": "/js/main.f37be265024e0905fdb1.hot-update.js",
"/js/main.87f74b80f2503a0d728c.hot-update.js": "/js/main.87f74b80f2503a0d728c.hot-update.js", "/js/main.2ff5bc2e3fc33bb10ae8.hot-update.js": "/js/main.2ff5bc2e3fc33bb10ae8.hot-update.js",
"/js/main.3ebaa3668f23c6e26548.hot-update.js": "/js/main.3ebaa3668f23c6e26548.hot-update.js", "/js/main.c1fea30dd4b2ae2f5ee4.hot-update.js": "/js/main.c1fea30dd4b2ae2f5ee4.hot-update.js",
"/js/main.15057417af28b6c6d6ff.hot-update.js": "/js/main.15057417af28b6c6d6ff.hot-update.js", "/js/main.524c8af30cc872a60dbc.hot-update.js": "/js/main.524c8af30cc872a60dbc.hot-update.js",
"/js/main.2e7c576dbf2e7503b8ef.hot-update.js": "/js/main.2e7c576dbf2e7503b8ef.hot-update.js", "/js/main.38f17119314e4cf7ee30.hot-update.js": "/js/main.38f17119314e4cf7ee30.hot-update.js",
"/js/main.47f584845c3310757ea5.hot-update.js": "/js/main.47f584845c3310757ea5.hot-update.js", "/js/main.000365b3771096c22493.hot-update.js": "/js/main.000365b3771096c22493.hot-update.js",
"/js/main.6f7b50d4c52af64a51c2.hot-update.js": "/js/main.6f7b50d4c52af64a51c2.hot-update.js", "/js/main.5058b71aee8180779088.hot-update.js": "/js/main.5058b71aee8180779088.hot-update.js",
"/js/main.265e3ed2100aeb1cfff9.hot-update.js": "/js/main.265e3ed2100aeb1cfff9.hot-update.js", "/js/main.28a6e654db8570610bcc.hot-update.js": "/js/main.28a6e654db8570610bcc.hot-update.js",
"/js/main.a1cdd1c29ccf399b7141.hot-update.js": "/js/main.a1cdd1c29ccf399b7141.hot-update.js", "/js/main.01d58ffd9ede26b4c7e8.hot-update.js": "/js/main.01d58ffd9ede26b4c7e8.hot-update.js",
"/js/main.49bbabe613088b451c19.hot-update.js": "/js/main.49bbabe613088b451c19.hot-update.js", "/js/main.a47698cf5dcdb2d76743.hot-update.js": "/js/main.a47698cf5dcdb2d76743.hot-update.js",
"/js/main.3629824777dab206f9ee.hot-update.js": "/js/main.3629824777dab206f9ee.hot-update.js", "/js/main.0deb25a3a78ab4daa09d.hot-update.js": "/js/main.0deb25a3a78ab4daa09d.hot-update.js",
"/js/main.f61e6adbbe20e94ca550.hot-update.js": "/js/main.f61e6adbbe20e94ca550.hot-update.js", "/js/main.6d287221e3051a0e8b19.hot-update.js": "/js/main.6d287221e3051a0e8b19.hot-update.js",
"/js/main.2ee91f5e4302b51c0279.hot-update.js": "/js/main.2ee91f5e4302b51c0279.hot-update.js", "/js/main.03545060f23e17d4713e.hot-update.js": "/js/main.03545060f23e17d4713e.hot-update.js",
"/js/main.3f8e72c90f8f9bc89569.hot-update.js": "/js/main.3f8e72c90f8f9bc89569.hot-update.js", "/js/main.4ab8325d22ec6abd7b0f.hot-update.js": "/js/main.4ab8325d22ec6abd7b0f.hot-update.js",
"/js/main.d0a86f9a8cb2e673cf37.hot-update.js": "/js/main.d0a86f9a8cb2e673cf37.hot-update.js", "/js/main.00fed1dd652c91f7f1ed.hot-update.js": "/js/main.00fed1dd652c91f7f1ed.hot-update.js",
"/js/main.d0bc3040ff8aa0112594.hot-update.js": "/js/main.d0bc3040ff8aa0112594.hot-update.js", "/js/main.5df7c1faa518dd9e7374.hot-update.js": "/js/main.5df7c1faa518dd9e7374.hot-update.js",
"/js/main.26dc29e6caa621492b9d.hot-update.js": "/js/main.26dc29e6caa621492b9d.hot-update.js", "/js/main.3240217125bf1aaab215.hot-update.js": "/js/main.3240217125bf1aaab215.hot-update.js",
"/js/main.94a0a85c82278f47e6d0.hot-update.js": "/js/main.94a0a85c82278f47e6d0.hot-update.js", "/js/main.b829d78aae8e8943c38a.hot-update.js": "/js/main.b829d78aae8e8943c38a.hot-update.js",
"/js/main.c1f1327a107c7e877d26.hot-update.js": "/js/main.c1f1327a107c7e877d26.hot-update.js", "/js/main.ec038eace3860c991d40.hot-update.js": "/js/main.ec038eace3860c991d40.hot-update.js",
"/js/main.559481d20fb86a3590a6.hot-update.js": "/js/main.559481d20fb86a3590a6.hot-update.js", "/js/main.9dd62ce81e1450f065c8.hot-update.js": "/js/main.9dd62ce81e1450f065c8.hot-update.js",
"/js/main.5c9789988c31fd602b79.hot-update.js": "/js/main.5c9789988c31fd602b79.hot-update.js", "/js/main.628d9147c15e44160305.hot-update.js": "/js/main.628d9147c15e44160305.hot-update.js",
"/js/main.bc6cf3eca8ac4df9adc9.hot-update.js": "/js/main.bc6cf3eca8ac4df9adc9.hot-update.js", "/js/main.b39e39c8ec53f2a9ecca.hot-update.js": "/js/main.b39e39c8ec53f2a9ecca.hot-update.js",
"/js/main.b8c00841ae4b474b6a60.hot-update.js": "/js/main.b8c00841ae4b474b6a60.hot-update.js", "/js/main.a09f85c1baf4d028887a.hot-update.js": "/js/main.a09f85c1baf4d028887a.hot-update.js",
"/js/main.bd599b3896c244b6bef8.hot-update.js": "/js/main.bd599b3896c244b6bef8.hot-update.js", "/js/main.61dddfacfee5c8a77667.hot-update.js": "/js/main.61dddfacfee5c8a77667.hot-update.js",
"/js/main.2e36d672614a2914e23c.hot-update.js": "/js/main.2e36d672614a2914e23c.hot-update.js", "/js/main.ee62efcb284edede4f58.hot-update.js": "/js/main.ee62efcb284edede4f58.hot-update.js",
"/js/main.efdcc9d7e9c18a7d1cca.hot-update.js": "/js/main.efdcc9d7e9c18a7d1cca.hot-update.js", "/js/main.19b541f808631fef7b7f.hot-update.js": "/js/main.19b541f808631fef7b7f.hot-update.js",
"/js/main.c3f805fddf6ec0cbbb89.hot-update.js": "/js/main.c3f805fddf6ec0cbbb89.hot-update.js", "/js/main.75c1b183cc04db369604.hot-update.js": "/js/main.75c1b183cc04db369604.hot-update.js",
"/js/main.f0010dfcc8c31d3983c4.hot-update.js": "/js/main.f0010dfcc8c31d3983c4.hot-update.js", "/js/main.f91ca827a024ad1b9e98.hot-update.js": "/js/main.f91ca827a024ad1b9e98.hot-update.js",
"/js/main.4d8045b6b3778a4ffeb1.hot-update.js": "/js/main.4d8045b6b3778a4ffeb1.hot-update.js", "/js/main.b543fd24ddf34dc69882.hot-update.js": "/js/main.b543fd24ddf34dc69882.hot-update.js",
"/js/main.63238fe535516168d1e7.hot-update.js": "/js/main.63238fe535516168d1e7.hot-update.js", "/js/main.b4772a0733f53e1c6a39.hot-update.js": "/js/main.b4772a0733f53e1c6a39.hot-update.js",
"/js/main.b7a962e7b5a90c9e579d.hot-update.js": "/js/main.b7a962e7b5a90c9e579d.hot-update.js", "/js/main.af68b834210e123e14c7.hot-update.js": "/js/main.af68b834210e123e14c7.hot-update.js",
"/js/main.76e8ea5fa9f6362843f4.hot-update.js": "/js/main.76e8ea5fa9f6362843f4.hot-update.js", "/js/main.53a01851e7713425ec46.hot-update.js": "/js/main.53a01851e7713425ec46.hot-update.js",
"/js/main.c8046fb0c18ae8914591.hot-update.js": "/js/main.c8046fb0c18ae8914591.hot-update.js", "/js/main.06d1129f0be150e1154f.hot-update.js": "/js/main.06d1129f0be150e1154f.hot-update.js",
"/js/main.56a0bedf8508feb6dcd1.hot-update.js": "/js/main.56a0bedf8508feb6dcd1.hot-update.js", "/js/main.fc265c9c466f48229905.hot-update.js": "/js/main.fc265c9c466f48229905.hot-update.js",
"/js/main.45f799303e37fe182724.hot-update.js": "/js/main.45f799303e37fe182724.hot-update.js", "/js/main.93d73da79cd8b4b7a6a4.hot-update.js": "/js/main.93d73da79cd8b4b7a6a4.hot-update.js",
"/js/main.d87252dc4e16fcb8bc96.hot-update.js": "/js/main.d87252dc4e16fcb8bc96.hot-update.js", "/js/main.e0293848e03669e8c954.hot-update.js": "/js/main.e0293848e03669e8c954.hot-update.js",
"/js/main.ff82fb4dbf064fdd1911.hot-update.js": "/js/main.ff82fb4dbf064fdd1911.hot-update.js", "/js/main.6e70caff0ff123768e56.hot-update.js": "/js/main.6e70caff0ff123768e56.hot-update.js",
"/js/main.c6edce5f0759812ca8c7.hot-update.js": "/js/main.c6edce5f0759812ca8c7.hot-update.js", "/js/main.88b731ba24fba095863f.hot-update.js": "/js/main.88b731ba24fba095863f.hot-update.js",
"/js/main.80d40749e6b1bc770e96.hot-update.js": "/js/main.80d40749e6b1bc770e96.hot-update.js", "/js/main.245cc75ac6bc6c5fdda3.hot-update.js": "/js/main.245cc75ac6bc6c5fdda3.hot-update.js",
"/js/main.4cafd635a783e9702759.hot-update.js": "/js/main.4cafd635a783e9702759.hot-update.js", "/js/main.a51faf0bcad8e93f6a60.hot-update.js": "/js/main.a51faf0bcad8e93f6a60.hot-update.js",
"/js/main.159c429673622ebaa505.hot-update.js": "/js/main.159c429673622ebaa505.hot-update.js", "/js/main.f75d45ce3a4071deb3eb.hot-update.js": "/js/main.f75d45ce3a4071deb3eb.hot-update.js",
"/js/main.4d4ae0fe9c2255751bb3.hot-update.js": "/js/main.4d4ae0fe9c2255751bb3.hot-update.js", "/js/main.0da2bc38a9c17f7da812.hot-update.js": "/js/main.0da2bc38a9c17f7da812.hot-update.js",
"/js/main.3e60eec6d7f085ff9225.hot-update.js": "/js/main.3e60eec6d7f085ff9225.hot-update.js", "/js/main.3f2257dc78861e76cbd9.hot-update.js": "/js/main.3f2257dc78861e76cbd9.hot-update.js",
"/js/main.729555b6d04375fd4a8a.hot-update.js": "/js/main.729555b6d04375fd4a8a.hot-update.js", "/js/main.3dcef8f17c75125345b0.hot-update.js": "/js/main.3dcef8f17c75125345b0.hot-update.js",
"/js/main.dcbfd3cb4fec88c42272.hot-update.js": "/js/main.dcbfd3cb4fec88c42272.hot-update.js", "/js/main.b117a2c638971ac84268.hot-update.js": "/js/main.b117a2c638971ac84268.hot-update.js",
"/js/main.e1584ef8bce01d005b28.hot-update.js": "/js/main.e1584ef8bce01d005b28.hot-update.js", "/js/main.4bf80b5a98d048b19695.hot-update.js": "/js/main.4bf80b5a98d048b19695.hot-update.js",
"/js/main.76e6657c065ab87d5326.hot-update.js": "/js/main.76e6657c065ab87d5326.hot-update.js", "/js/main.c0587c1799dd105b8f06.hot-update.js": "/js/main.c0587c1799dd105b8f06.hot-update.js",
"/js/main.2a6301937a1f4a05986a.hot-update.js": "/js/main.2a6301937a1f4a05986a.hot-update.js", "/js/main.d6ce19d1db5909a2bd5b.hot-update.js": "/js/main.d6ce19d1db5909a2bd5b.hot-update.js",
"/js/main.5dc207685cdfd4232d6c.hot-update.js": "/js/main.5dc207685cdfd4232d6c.hot-update.js", "/js/main.3cc9faf0401241b3e048.hot-update.js": "/js/main.3cc9faf0401241b3e048.hot-update.js",
"/js/main.d87c42a5eae1eed09b9f.hot-update.js": "/js/main.d87c42a5eae1eed09b9f.hot-update.js", "/js/main.c03bc3a1e01506572f77.hot-update.js": "/js/main.c03bc3a1e01506572f77.hot-update.js",
"/js/main.5065c095791a58f08388.hot-update.js": "/js/main.5065c095791a58f08388.hot-update.js", "/js/main.2cb9f7a624da6378e8f1.hot-update.js": "/js/main.2cb9f7a624da6378e8f1.hot-update.js",
"/js/main.ae9c1527836164e6221f.hot-update.js": "/js/main.ae9c1527836164e6221f.hot-update.js", "/js/main.5203503b49254ae57b54.hot-update.js": "/js/main.5203503b49254ae57b54.hot-update.js",
"/js/main.64a5249c19f287d3d9f5.hot-update.js": "/js/main.64a5249c19f287d3d9f5.hot-update.js", "/js/main.6be457bcad68d70a1adf.hot-update.js": "/js/main.6be457bcad68d70a1adf.hot-update.js",
"/js/main.649b1010030c522abafd.hot-update.js": "/js/main.649b1010030c522abafd.hot-update.js", "/js/main.f366a0af0158f83a41d3.hot-update.js": "/js/main.f366a0af0158f83a41d3.hot-update.js",
"/js/main.c0a2c9b1daac8ce0f3d0.hot-update.js": "/js/main.c0a2c9b1daac8ce0f3d0.hot-update.js", "/js/main.0064e8a3066adf5ac021.hot-update.js": "/js/main.0064e8a3066adf5ac021.hot-update.js",
"/js/main.972d9ef69861ee475711.hot-update.js": "/js/main.972d9ef69861ee475711.hot-update.js", "/js/main.4e54ef87ab2545505191.hot-update.js": "/js/main.4e54ef87ab2545505191.hot-update.js",
"/js/main.c32dea75a0e744ee5f94.hot-update.js": "/js/main.c32dea75a0e744ee5f94.hot-update.js", "/js/main.2fa133eeaaaa33ba84ab.hot-update.js": "/js/main.2fa133eeaaaa33ba84ab.hot-update.js",
"/js/main.27ed4923387fde8852c0.hot-update.js": "/js/main.27ed4923387fde8852c0.hot-update.js", "/js/main.b7287bff96140dcf41c2.hot-update.js": "/js/main.b7287bff96140dcf41c2.hot-update.js",
"/js/main.b781ce03cd18bb1d9d5e.hot-update.js": "/js/main.b781ce03cd18bb1d9d5e.hot-update.js", "/js/main.59d4f07c707c45f90460.hot-update.js": "/js/main.59d4f07c707c45f90460.hot-update.js",
"/js/main.28a9f4094f796095da8b.hot-update.js": "/js/main.28a9f4094f796095da8b.hot-update.js", "/js/main.33e725bc42c4cf38e7e4.hot-update.js": "/js/main.33e725bc42c4cf38e7e4.hot-update.js",
"/js/main.c68844e921f5789877d2.hot-update.js": "/js/main.c68844e921f5789877d2.hot-update.js", "/js/main.4e722435b423aa82857d.hot-update.js": "/js/main.4e722435b423aa82857d.hot-update.js",
"/js/main.f44d6bffeb4bae9b7fcb.hot-update.js": "/js/main.f44d6bffeb4bae9b7fcb.hot-update.js", "/js/main.1136db92d52691c471d3.hot-update.js": "/js/main.1136db92d52691c471d3.hot-update.js",
"/js/main.06a4ffee2720e4f15696.hot-update.js": "/js/main.06a4ffee2720e4f15696.hot-update.js", "/js/main.dd31325ca820b342d735.hot-update.js": "/js/main.dd31325ca820b342d735.hot-update.js",
"/js/main.d9eb5e7ec061ebbfe5c4.hot-update.js": "/js/main.d9eb5e7ec061ebbfe5c4.hot-update.js", "/js/main.bdf334ccc5eca22b3f5b.hot-update.js": "/js/main.bdf334ccc5eca22b3f5b.hot-update.js",
"/js/main.9138c4a10256e728b68b.hot-update.js": "/js/main.9138c4a10256e728b68b.hot-update.js", "/js/main.7e8276e3feeb929dcce1.hot-update.js": "/js/main.7e8276e3feeb929dcce1.hot-update.js",
"/js/main.7b061ee41f753b148fbd.hot-update.js": "/js/main.7b061ee41f753b148fbd.hot-update.js", "/js/main.0308b12b1f1eb3efe683.hot-update.js": "/js/main.0308b12b1f1eb3efe683.hot-update.js",
"/js/main.38d6a637176f773c8985.hot-update.js": "/js/main.38d6a637176f773c8985.hot-update.js", "/js/main.8cb3460b3923a30a8ae5.hot-update.js": "/js/main.8cb3460b3923a30a8ae5.hot-update.js",
"/js/main.70944d4f89514e3effee.hot-update.js": "/js/main.70944d4f89514e3effee.hot-update.js", "/js/main.b9e3f9e983c99811e94d.hot-update.js": "/js/main.b9e3f9e983c99811e94d.hot-update.js",
"/js/main.40c67fc38208df539d4c.hot-update.js": "/js/main.40c67fc38208df539d4c.hot-update.js", "/js/main.9dbdfaa763040e52d396.hot-update.js": "/js/main.9dbdfaa763040e52d396.hot-update.js",
"/js/main.6f7ed9c50316e7523474.hot-update.js": "/js/main.6f7ed9c50316e7523474.hot-update.js", "/js/main.fde767d3f7b0771781e1.hot-update.js": "/js/main.fde767d3f7b0771781e1.hot-update.js",
"/js/main.545a2398e13034f72d5d.hot-update.js": "/js/main.545a2398e13034f72d5d.hot-update.js", "/js/main.7a961f271dce3dbff59b.hot-update.js": "/js/main.7a961f271dce3dbff59b.hot-update.js",
"/js/main.efdaad340c2eaf7fefa8.hot-update.js": "/js/main.efdaad340c2eaf7fefa8.hot-update.js", "/js/main.f77291d2e605a33650f2.hot-update.js": "/js/main.f77291d2e605a33650f2.hot-update.js",
"/js/main.4405e058ab82bd702872.hot-update.js": "/js/main.4405e058ab82bd702872.hot-update.js", "/js/main.5b4ddbd60a78a4c2dc8b.hot-update.js": "/js/main.5b4ddbd60a78a4c2dc8b.hot-update.js",
"/js/main.f95ffc2b0acc559669a1.hot-update.js": "/js/main.f95ffc2b0acc559669a1.hot-update.js", "/js/main.d86b987275b9c5756dcc.hot-update.js": "/js/main.d86b987275b9c5756dcc.hot-update.js",
"/js/main.b9e8c22f30099babdfcf.hot-update.js": "/js/main.b9e8c22f30099babdfcf.hot-update.js", "/js/main.868816b50cf2e8e4f191.hot-update.js": "/js/main.868816b50cf2e8e4f191.hot-update.js",
"/js/main.28c69286fc5bbd88176b.hot-update.js": "/js/main.28c69286fc5bbd88176b.hot-update.js", "/js/main.18355b098cdaa38c0930.hot-update.js": "/js/main.18355b098cdaa38c0930.hot-update.js",
"/js/main.5558aa165c02e9c36faa.hot-update.js": "/js/main.5558aa165c02e9c36faa.hot-update.js", "/js/main.b32cfff8a52466b61225.hot-update.js": "/js/main.b32cfff8a52466b61225.hot-update.js",
"/js/main.4681acdd50e76b32b9df.hot-update.js": "/js/main.4681acdd50e76b32b9df.hot-update.js", "/js/main.1993377815652ba309fd.hot-update.js": "/js/main.1993377815652ba309fd.hot-update.js",
"/js/main.562dc5f8d4b3a6326e2d.hot-update.js": "/js/main.562dc5f8d4b3a6326e2d.hot-update.js", "/js/main.fe4028eff94af9a2bf88.hot-update.js": "/js/main.fe4028eff94af9a2bf88.hot-update.js",
"/js/main.066e6206fb05b11e1f53.hot-update.js": "/js/main.066e6206fb05b11e1f53.hot-update.js", "/js/main.08dc41013d4cc7939976.hot-update.js": "/js/main.08dc41013d4cc7939976.hot-update.js",
"/js/main.74bc2e901432b3328d55.hot-update.js": "/js/main.74bc2e901432b3328d55.hot-update.js", "/js/main.80a7d45a34fa8668a4cf.hot-update.js": "/js/main.80a7d45a34fa8668a4cf.hot-update.js",
"/js/main.6e57009fbf2f7ace0a7d.hot-update.js": "/js/main.6e57009fbf2f7ace0a7d.hot-update.js", "/js/main.2d6d0f1aae82e0df42f8.hot-update.js": "/js/main.2d6d0f1aae82e0df42f8.hot-update.js",
"/js/main.f373c159b86b501322a6.hot-update.js": "/js/main.f373c159b86b501322a6.hot-update.js", "/js/main.85f1c5f01b37914d6d6f.hot-update.js": "/js/main.85f1c5f01b37914d6d6f.hot-update.js",
"/js/main.ecc10091727a02da6a9c.hot-update.js": "/js/main.ecc10091727a02da6a9c.hot-update.js", "/js/main.c7d70a2e5bada81377d5.hot-update.js": "/js/main.c7d70a2e5bada81377d5.hot-update.js",
"/js/main.adc54d65eed9e5c61556.hot-update.js": "/js/main.adc54d65eed9e5c61556.hot-update.js", "/js/main.9d41fbc28b830e37de0d.hot-update.js": "/js/main.9d41fbc28b830e37de0d.hot-update.js",
"/js/main.c01bb771108f9c646020.hot-update.js": "/js/main.c01bb771108f9c646020.hot-update.js", "/js/main.d014a0642bc91b6c99a0.hot-update.js": "/js/main.d014a0642bc91b6c99a0.hot-update.js",
"/js/main.6593cd8c148a84646c33.hot-update.js": "/js/main.6593cd8c148a84646c33.hot-update.js", "/js/main.e0998058ab9ee490f113.hot-update.js": "/js/main.e0998058ab9ee490f113.hot-update.js",
"/js/main.33234ee70a9719fb2579.hot-update.js": "/js/main.33234ee70a9719fb2579.hot-update.js", "/js/main.0cd5a95891b3d2a30d01.hot-update.js": "/js/main.0cd5a95891b3d2a30d01.hot-update.js",
"/js/main.ed594c8318b95e885daf.hot-update.js": "/js/main.ed594c8318b95e885daf.hot-update.js", "/js/main.cdc6ab5486c3de2989ca.hot-update.js": "/js/main.cdc6ab5486c3de2989ca.hot-update.js",
"/js/main.dcac8a3e2cf929ff4a94.hot-update.js": "/js/main.dcac8a3e2cf929ff4a94.hot-update.js", "/js/main.0366f168bde7215a81f5.hot-update.js": "/js/main.0366f168bde7215a81f5.hot-update.js",
"/js/main.e0dd196c2d6dbc12b1cc.hot-update.js": "/js/main.e0dd196c2d6dbc12b1cc.hot-update.js", "/js/main.63176e7e6fc742804a08.hot-update.js": "/js/main.63176e7e6fc742804a08.hot-update.js",
"/js/main.b4889018e0728536d393.hot-update.js": "/js/main.b4889018e0728536d393.hot-update.js", "/js/main.39d6b27892fc6363587c.hot-update.js": "/js/main.39d6b27892fc6363587c.hot-update.js",
"/js/main.d06f2e10dcca2d1fd48b.hot-update.js": "/js/main.d06f2e10dcca2d1fd48b.hot-update.js", "/js/main.a790ca39feedf2af0b92.hot-update.js": "/js/main.a790ca39feedf2af0b92.hot-update.js",
"/js/main.8acb4fb977f37099e6a9.hot-update.js": "/js/main.8acb4fb977f37099e6a9.hot-update.js", "/js/main.7db25e07f15cfb581128.hot-update.js": "/js/main.7db25e07f15cfb581128.hot-update.js",
"/js/main.733284992c576b1d4857.hot-update.js": "/js/main.733284992c576b1d4857.hot-update.js", "/js/main.87b661d365bd9adbfb09.hot-update.js": "/js/main.87b661d365bd9adbfb09.hot-update.js",
"/js/main.0953370e9bc3130d11ba.hot-update.js": "/js/main.0953370e9bc3130d11ba.hot-update.js", "/js/main.5884a13a2b4f3076e708.hot-update.js": "/js/main.5884a13a2b4f3076e708.hot-update.js",
"/js/main.4ec217ab92f02f6b3808.hot-update.js": "/js/main.4ec217ab92f02f6b3808.hot-update.js", "/js/main.d08aa3e62dd4f0b7125a.hot-update.js": "/js/main.d08aa3e62dd4f0b7125a.hot-update.js",
"/js/main.2ba1fae1f9083614ad8c.hot-update.js": "/js/main.2ba1fae1f9083614ad8c.hot-update.js", "/js/main.08930a79faad10ca5ae5.hot-update.js": "/js/main.08930a79faad10ca5ae5.hot-update.js",
"/js/main.2ffb560768c76cbceb47.hot-update.js": "/js/main.2ffb560768c76cbceb47.hot-update.js", "/js/main.3852385059eaf25596ce.hot-update.js": "/js/main.3852385059eaf25596ce.hot-update.js",
"/js/main.ae152ee74174b7c61919.hot-update.js": "/js/main.ae152ee74174b7c61919.hot-update.js", "/js/main.d401d0dba62fd43758f8.hot-update.js": "/js/main.d401d0dba62fd43758f8.hot-update.js",
"/js/main.e807166c583485b287f4.hot-update.js": "/js/main.e807166c583485b287f4.hot-update.js", "/js/main.9ac63b64bbe3d715f73a.hot-update.js": "/js/main.9ac63b64bbe3d715f73a.hot-update.js",
"/js/main.f5d0806be3ea5a7fb07b.hot-update.js": "/js/main.f5d0806be3ea5a7fb07b.hot-update.js", "/js/main.29b25d87761afe9deda1.hot-update.js": "/js/main.29b25d87761afe9deda1.hot-update.js",
"/js/main.27671330e1991c634a33.hot-update.js": "/js/main.27671330e1991c634a33.hot-update.js", "/js/main.e97f2cc4e121eccaced8.hot-update.js": "/js/main.e97f2cc4e121eccaced8.hot-update.js",
"/js/main.641b4556506c6b97286d.hot-update.js": "/js/main.641b4556506c6b97286d.hot-update.js", "/js/main.6688b2fa8b07b72c00a5.hot-update.js": "/js/main.6688b2fa8b07b72c00a5.hot-update.js",
"/js/main.0962b36d9c473537d2d4.hot-update.js": "/js/main.0962b36d9c473537d2d4.hot-update.js", "/js/main.f98aa05a83ee48c7095d.hot-update.js": "/js/main.f98aa05a83ee48c7095d.hot-update.js",
"/js/main.16bcaf0e1165ef3fc23d.hot-update.js": "/js/main.16bcaf0e1165ef3fc23d.hot-update.js", "/js/main.69a434849703a9c4835a.hot-update.js": "/js/main.69a434849703a9c4835a.hot-update.js",
"/js/main.f48ba321f0d840249a4a.hot-update.js": "/js/main.f48ba321f0d840249a4a.hot-update.js", "/js/main.3b667b3ed35e0d663643.hot-update.js": "/js/main.3b667b3ed35e0d663643.hot-update.js",
"/js/main.7b57598614a7f8aa8d97.hot-update.js": "/js/main.7b57598614a7f8aa8d97.hot-update.js", "/js/main.0d696ab53807ff69c8e6.hot-update.js": "/js/main.0d696ab53807ff69c8e6.hot-update.js",
"/js/main.9e93e203f69daceb8e4d.hot-update.js": "/js/main.9e93e203f69daceb8e4d.hot-update.js", "/js/main.0540d9a5f02fae3e2d11.hot-update.js": "/js/main.0540d9a5f02fae3e2d11.hot-update.js",
"/js/main.8b5a75a61d01d67371d0.hot-update.js": "/js/main.8b5a75a61d01d67371d0.hot-update.js", "/js/main.3976628bbd6e48b63263.hot-update.js": "/js/main.3976628bbd6e48b63263.hot-update.js",
"/js/main.61561f638b169a63c4c3.hot-update.js": "/js/main.61561f638b169a63c4c3.hot-update.js", "/js/main.12ac2eba6a990651f796.hot-update.js": "/js/main.12ac2eba6a990651f796.hot-update.js",
"/js/main.f2ed949ef8596b9b9eae.hot-update.js": "/js/main.f2ed949ef8596b9b9eae.hot-update.js", "/js/main.9526e857eddc882a0586.hot-update.js": "/js/main.9526e857eddc882a0586.hot-update.js",
"/js/main.2aaaeac0077d05ee844c.hot-update.js": "/js/main.2aaaeac0077d05ee844c.hot-update.js", "/js/main.da0dcd2c8116fa937c70.hot-update.js": "/js/main.da0dcd2c8116fa937c70.hot-update.js",
"/js/main.6ecca9cb2b4220a353d0.hot-update.js": "/js/main.6ecca9cb2b4220a353d0.hot-update.js", "/js/main.b4780116904dd809e0b2.hot-update.js": "/js/main.b4780116904dd809e0b2.hot-update.js",
"/js/main.ccc950ff1c199f694651.hot-update.js": "/js/main.ccc950ff1c199f694651.hot-update.js", "/js/main.99c984516e593db7344f.hot-update.js": "/js/main.99c984516e593db7344f.hot-update.js",
"/js/main.1cfc1f5ab8663c7bb239.hot-update.js": "/js/main.1cfc1f5ab8663c7bb239.hot-update.js", "/js/main.0f0812654b82b48f46ce.hot-update.js": "/js/main.0f0812654b82b48f46ce.hot-update.js",
"/js/main.16c9de12186cd01066fe.hot-update.js": "/js/main.16c9de12186cd01066fe.hot-update.js", "/js/main.961e3e3fa905b0856176.hot-update.js": "/js/main.961e3e3fa905b0856176.hot-update.js",
"/js/main.d823beda1b1570be851d.hot-update.js": "/js/main.d823beda1b1570be851d.hot-update.js", "/js/main.29e365ef73f45c925a53.hot-update.js": "/js/main.29e365ef73f45c925a53.hot-update.js",
"/js/main.eeb0ffd23c5df9eb221e.hot-update.js": "/js/main.eeb0ffd23c5df9eb221e.hot-update.js", "/js/main.60f3cc9526aa07f76b92.hot-update.js": "/js/main.60f3cc9526aa07f76b92.hot-update.js",
"/js/main.5e7e600792bb71b7db68.hot-update.js": "/js/main.5e7e600792bb71b7db68.hot-update.js", "/js/main.efd59b161078632b87e7.hot-update.js": "/js/main.efd59b161078632b87e7.hot-update.js",
"/js/main.e1d4c929ab4f8b777d47.hot-update.js": "/js/main.e1d4c929ab4f8b777d47.hot-update.js", "/js/main.d314b501f03773483b7b.hot-update.js": "/js/main.d314b501f03773483b7b.hot-update.js",
"/js/main.45f7944bb3af35922c64.hot-update.js": "/js/main.45f7944bb3af35922c64.hot-update.js", "/js/main.7142fa80d2d0bc8d87b2.hot-update.js": "/js/main.7142fa80d2d0bc8d87b2.hot-update.js",
"/js/main.89989cef2a8d1eb393f1.hot-update.js": "/js/main.89989cef2a8d1eb393f1.hot-update.js", "/js/main.81965d3c889e7772119c.hot-update.js": "/js/main.81965d3c889e7772119c.hot-update.js",
"/js/main.4971dfeda8fda570d2fa.hot-update.js": "/js/main.4971dfeda8fda570d2fa.hot-update.js", "/js/main.398343de9241f273f4c7.hot-update.js": "/js/main.398343de9241f273f4c7.hot-update.js",
"/js/main.7e430f3a435737227103.hot-update.js": "/js/main.7e430f3a435737227103.hot-update.js", "/js/main.6e395602900d2a3cc85c.hot-update.js": "/js/main.6e395602900d2a3cc85c.hot-update.js",
"/js/main.8d06bacc4373bd3cca5f.hot-update.js": "/js/main.8d06bacc4373bd3cca5f.hot-update.js", "/js/main.50508c2caf36b35e114c.hot-update.js": "/js/main.50508c2caf36b35e114c.hot-update.js",
"/js/main.193bbf6cb7a0cbaf3705.hot-update.js": "/js/main.193bbf6cb7a0cbaf3705.hot-update.js", "/js/main.f3addf09487bb7a2bf69.hot-update.js": "/js/main.f3addf09487bb7a2bf69.hot-update.js",
"/js/main.fbab26416a02c186c906.hot-update.js": "/js/main.fbab26416a02c186c906.hot-update.js", "/js/main.e83c390ebbba1ab74aaf.hot-update.js": "/js/main.e83c390ebbba1ab74aaf.hot-update.js",
"/js/main.000b68c1b5279e3d418b.hot-update.js": "/js/main.000b68c1b5279e3d418b.hot-update.js", "/js/main.0454c8e796e648bbe937.hot-update.js": "/js/main.0454c8e796e648bbe937.hot-update.js",
"/js/main.cc3e70b1fa1b8bfc7f21.hot-update.js": "/js/main.cc3e70b1fa1b8bfc7f21.hot-update.js", "/js/main.34be65f36719f7eaeea6.hot-update.js": "/js/main.34be65f36719f7eaeea6.hot-update.js",
"/js/main.5e36ed4ac236566fc1ef.hot-update.js": "/js/main.5e36ed4ac236566fc1ef.hot-update.js", "/js/main.b97be0d2cb9221a5b358.hot-update.js": "/js/main.b97be0d2cb9221a5b358.hot-update.js"
"/js/main.874c9b2920b24f744f37.hot-update.js": "/js/main.874c9b2920b24f744f37.hot-update.js",
"/js/main.5532d50ebe0577822aa0.hot-update.js": "/js/main.5532d50ebe0577822aa0.hot-update.js",
"/js/main.890ae7e6f9143a263bcd.hot-update.js": "/js/main.890ae7e6f9143a263bcd.hot-update.js",
"/js/main.52a6f58edde8fcc06a56.hot-update.js": "/js/main.52a6f58edde8fcc06a56.hot-update.js",
"/js/main.a9710a976ccdf0ef75ec.hot-update.js": "/js/main.a9710a976ccdf0ef75ec.hot-update.js",
"/js/main.7c116a2375fb6011c0df.hot-update.js": "/js/main.7c116a2375fb6011c0df.hot-update.js",
"/js/main.8bf61630c11938aa868a.hot-update.js": "/js/main.8bf61630c11938aa868a.hot-update.js",
"/js/main.6a65ae1a508c784cef20.hot-update.js": "/js/main.6a65ae1a508c784cef20.hot-update.js",
"/js/main.780e4703e1c4eef0224a.hot-update.js": "/js/main.780e4703e1c4eef0224a.hot-update.js",
"/js/main.915aac92a42985ed75d9.hot-update.js": "/js/main.915aac92a42985ed75d9.hot-update.js",
"/js/main.0ed70fed65b100346771.hot-update.js": "/js/main.0ed70fed65b100346771.hot-update.js",
"/js/main.b5823ed93ed9ab9584a2.hot-update.js": "/js/main.b5823ed93ed9ab9584a2.hot-update.js",
"/js/main.059e7929ca5a3aaf71aa.hot-update.js": "/js/main.059e7929ca5a3aaf71aa.hot-update.js",
"/js/main.64f79855bd9b00f7bcfd.hot-update.js": "/js/main.64f79855bd9b00f7bcfd.hot-update.js",
"/js/main.fb1320b8883c557e5e92.hot-update.js": "/js/main.fb1320b8883c557e5e92.hot-update.js",
"/js/main.e55d30d37707a3fa23f2.hot-update.js": "/js/main.e55d30d37707a3fa23f2.hot-update.js",
"/js/main.f9ffa503d13670e554c2.hot-update.js": "/js/main.f9ffa503d13670e554c2.hot-update.js",
"/js/main.2b98e2a5ed1bfa7b6e5e.hot-update.js": "/js/main.2b98e2a5ed1bfa7b6e5e.hot-update.js",
"/js/main.3e07bec6d4786727259e.hot-update.js": "/js/main.3e07bec6d4786727259e.hot-update.js",
"/js/main.a06a96f7eb0659afae56.hot-update.js": "/js/main.a06a96f7eb0659afae56.hot-update.js",
"/js/main.8d6337331ed349f2c8ef.hot-update.js": "/js/main.8d6337331ed349f2c8ef.hot-update.js",
"/js/main.e8d22bd65491d1fbc68a.hot-update.js": "/js/main.e8d22bd65491d1fbc68a.hot-update.js",
"/js/main.86a0433aa86e090d18ea.hot-update.js": "/js/main.86a0433aa86e090d18ea.hot-update.js",
"/js/main.5c8a7a29efa889c4e53b.hot-update.js": "/js/main.5c8a7a29efa889c4e53b.hot-update.js",
"/js/main.54efaf1fc562b7272951.hot-update.js": "/js/main.54efaf1fc562b7272951.hot-update.js",
"/js/main.4ff7195cb661704a1b81.hot-update.js": "/js/main.4ff7195cb661704a1b81.hot-update.js",
"/js/main.f71c27e89fb68d825c05.hot-update.js": "/js/main.f71c27e89fb68d825c05.hot-update.js",
"/js/main.194897f06249fb3f9962.hot-update.js": "/js/main.194897f06249fb3f9962.hot-update.js",
"/js/main.2c809a243eb4077162c7.hot-update.js": "/js/main.2c809a243eb4077162c7.hot-update.js",
"/js/main.1713b0f495c5d9d53546.hot-update.js": "/js/main.1713b0f495c5d9d53546.hot-update.js",
"/js/main.86ad2ff758d49f076ab1.hot-update.js": "/js/main.86ad2ff758d49f076ab1.hot-update.js",
"/js/main.419fc5ba2f8ecd16d39b.hot-update.js": "/js/main.419fc5ba2f8ecd16d39b.hot-update.js",
"/js/main.b538a683b679ff610516.hot-update.js": "/js/main.b538a683b679ff610516.hot-update.js",
"/js/main.0180cddd1715177635a7.hot-update.js": "/js/main.0180cddd1715177635a7.hot-update.js",
"/js/main.e926ded105d4c8b8efa0.hot-update.js": "/js/main.e926ded105d4c8b8efa0.hot-update.js",
"/js/main.3a536043ccdac53259d7.hot-update.js": "/js/main.3a536043ccdac53259d7.hot-update.js",
"/js/main.1e594e2adeeb7d5aa159.hot-update.js": "/js/main.1e594e2adeeb7d5aa159.hot-update.js",
"/js/main.f169e12ff8c4c10c509e.hot-update.js": "/js/main.f169e12ff8c4c10c509e.hot-update.js",
"/js/main.14147eeb76496b2cf4b4.hot-update.js": "/js/main.14147eeb76496b2cf4b4.hot-update.js",
"/js/main.3012dd5ecb02053093a5.hot-update.js": "/js/main.3012dd5ecb02053093a5.hot-update.js",
"/js/main.c14ef7924e4d0633c57b.hot-update.js": "/js/main.c14ef7924e4d0633c57b.hot-update.js",
"/js/main.c4927541644c755c14a4.hot-update.js": "/js/main.c4927541644c755c14a4.hot-update.js",
"/js/main.f00bcadde0f4c0441e5f.hot-update.js": "/js/main.f00bcadde0f4c0441e5f.hot-update.js",
"/js/main.a18e5a41067654213bcc.hot-update.js": "/js/main.a18e5a41067654213bcc.hot-update.js",
"/js/main.4b71496f1726c4006220.hot-update.js": "/js/main.4b71496f1726c4006220.hot-update.js",
"/js/main.79cfe2ae4a91cb940443.hot-update.js": "/js/main.79cfe2ae4a91cb940443.hot-update.js",
"/js/main.b38ec9ea3080c0b28f7b.hot-update.js": "/js/main.b38ec9ea3080c0b28f7b.hot-update.js",
"/js/main.522b7bd36270d98ca048.hot-update.js": "/js/main.522b7bd36270d98ca048.hot-update.js",
"/js/main.228cb36baa0c7b8e63f2.hot-update.js": "/js/main.228cb36baa0c7b8e63f2.hot-update.js",
"/js/main.1f8e416264c7b9d2ec41.hot-update.js": "/js/main.1f8e416264c7b9d2ec41.hot-update.js",
"/js/main.2226db028adde092f1d8.hot-update.js": "/js/main.2226db028adde092f1d8.hot-update.js",
"/js/main.27dfc30ba2c13d0b0b5c.hot-update.js": "/js/main.27dfc30ba2c13d0b0b5c.hot-update.js",
"/js/main.7e85d952d00d3346022a.hot-update.js": "/js/main.7e85d952d00d3346022a.hot-update.js",
"/js/main.e4a8de968ec92614dd63.hot-update.js": "/js/main.e4a8de968ec92614dd63.hot-update.js",
"/js/main.cb34728e0343ee07f04a.hot-update.js": "/js/main.cb34728e0343ee07f04a.hot-update.js",
"/js/main.1ac34f41e25905226a04.hot-update.js": "/js/main.1ac34f41e25905226a04.hot-update.js",
"/js/main.0cd80fb1e2a926ba3037.hot-update.js": "/js/main.0cd80fb1e2a926ba3037.hot-update.js"
} }
+31 -14
View File
@@ -1,21 +1,23 @@
<template> <template>
<div id="vue-file-manager" :class="appSize"> <div id="vue-file-manager" :class="appSize">
<div id="popups">
<div id="application-wrapper" v-if="layout === 'authorized'">
<!--Share Item setup-->
<ShareCreate />
<ShareEdit />
<!--Move item setup-->
<MoveItem />
<!--System alerts--> <!--System alerts-->
<Alert /> <Alert />
<!--Popup-->
<PopupMoveItem />
<!--Mobile Menu--> <!--Mobile Menu-->
<MobileOptionList /> <MobileMenu />
<!--Background vignette--> <!--Background vignette-->
<Vignette /> <Vignette />
</div>
<div id="application-wrapper" v-if="isLogged">
<!--Navigation Sidebar--> <!--Navigation Sidebar-->
<Sidebar/> <Sidebar/>
@@ -24,24 +26,30 @@
<router-view/> <router-view/>
</div> </div>
<router-view v-if="! isLogged" /> <router-view v-if="layout === 'unauthorized'"/>
</div> </div>
</template> </template>
<script> <script>
import MobileOptionList from '@/components/VueFileManagerComponents/FilesView/MobileOptionList' import MobileMenu from '@/components/VueFileManagerComponents/FilesView/MobileMenu'
import PopupMoveItem from '@/components/VueFileManagerComponents/Others/PopupMoveItem' import ShareCreate from '@/components/VueFileManagerComponents/Others/ShareCreate'
import ShareEdit from '@/components/VueFileManagerComponents/Others/ShareEdit'
import MoveItem from '@/components/VueFileManagerComponents/Others/MoveItem'
import Vignette from '@/components/VueFileManagerComponents/Others/Vignette' import Vignette from '@/components/VueFileManagerComponents/Others/Vignette'
import Alert from '@/components/VueFileManagerComponents/FilesView/Alert'
import Sidebar from '@/components/VueFileManagerComponents/Sidebar/Sidebar' import Sidebar from '@/components/VueFileManagerComponents/Sidebar/Sidebar'
import Alert from '@/components/VueFileManagerComponents/FilesView/Alert'
import {ResizeSensor} from 'css-element-queries' import {ResizeSensor} from 'css-element-queries'
import { includes } from 'lodash'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from "./bus"
export default { export default {
name: 'app', name: 'app',
components: { components: {
MobileOptionList, ShareCreate,
PopupMoveItem, MobileMenu,
ShareEdit,
MoveItem,
Vignette, Vignette,
Sidebar, Sidebar,
Alert, Alert,
@@ -50,6 +58,13 @@
...mapGetters([ ...mapGetters([
'appSize', 'isLogged', 'isGuest' 'appSize', 'isLogged', 'isGuest'
]), ]),
layout() {
if (includes(['VerifyByPassword', 'SharedContent', 'SignIn', 'SignUp', 'ForgottenPassword', 'CreateNewPassword'], this.$route.name)) {
return 'unauthorized'
}
return 'authorized'
}
}, },
methods: { methods: {
handleAppResize() { handleAppResize() {
@@ -71,6 +86,8 @@
}, },
mounted() { mounted() {
//events.$emit('share-item')
// Handle VueFileManager width // Handle VueFileManager width
var VueFileManager = document.getElementById('vue-file-manager'); var VueFileManager = document.getElementById('vue-file-manager');
new ResizeSensor(VueFileManager, this.handleAppResize); new ResizeSensor(VueFileManager, this.handleAppResize);
@@ -1,13 +1,16 @@
<template> <template>
<button class="button-base" :class="buttonStyle" type="button"> <button class="button-base" :class="buttonStyle" type="button">
<slot></slot> <span v-if="loading" class="icon">
<FontAwesomeIcon icon="sync-alt" class="sync-alt"/>
</span>
<slot v-if="! loading"></slot>
</button> </button>
</template> </template>
<script> <script>
export default { export default {
name: 'ButtonBase', name: 'ButtonBase',
props: ['buttonStyle'] props: ['buttonStyle', 'loading']
} }
</script> </script>
@@ -38,12 +41,30 @@
background: rgba($danger, .1); background: rgba($danger, .1);
} }
&.danger-solid {
color: white;
background: $danger;
}
&.secondary { &.secondary {
color: $text; color: $text;
background: $light_background; background: $light_background;
} }
} }
.sync-alt {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.button-base { .button-base {
@@ -1,34 +1,60 @@
<template> <template>
<div <div
ref="contextmenu"
class="contextmenu"
:style="{ top: positionY + 'px', left: positionX + 'px' }" :style="{ top: positionY + 'px', left: positionX + 'px' }"
@click="closeAndResetContextMenu"
class="contextmenu"
v-show="isVisible" v-show="isVisible"
ref="contextmenu"
> >
<ul class="menu-options" id="menu-options-list" ref="list" @click="closeAndResetContextMenu"> <!--ContextMenu for trash location-->
<ul v-if="$isTrashLocation()" class="menu-options" ref="list">
<li class="menu-option" @click="removeItem" v-if="item">
{{ $t('context_menu.delete') }}
</li>
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item">
{{ $t('context_menu.restore') }}
</li>
<li class="menu-option" @click="$store.dispatch('emptyTrash')">
{{ $t('context_menu.empty_trash') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul>
<!--View--> <!--ContextMenu for Base location-->
<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> <ul v-if="$isBaseLocation()" class="menu-options" ref="list">
<li class="menu-option" @click="createFolder" v-if="! $isTrashLocation()">{{ $t('context_menu.create_folder') }}</li> <li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
<!--Edits--> </li>
<li class="menu-option" @click="removeItem" v-if="! $isTrashLocation() && item">{{ $t('context_menu.delete') }}</li> <li class="menu-option" @click="createFolder">
<li class="menu-option" @click="moveItem" v-if="! $isTrashLocation() && item">{{ $t('context_menu.move') }}</li> {{ $t('context_menu.create_folder') }}
</li>
<!--Trash--> <li class="menu-option" @click="removeItem" v-if="item">
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item && $isTrashLocation()">{{ $t('context_menu.restore') }}</li> {{ $t('context_menu.delete') }}
<li class="menu-option" @click="$store.dispatch('emptyTrash')" v-if="$isTrashLocation()">{{ $t('context_menu.empty_trash') }}</li> </li>
<li class="menu-option" @click="moveItem" v-if="item">
<!--Others--> {{ $t('context_menu.move') }}
<li class="menu-option" @click="ItemDetail" v-if="item">{{ $t('context_menu.detail') }}</li> </li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">{{ $t('context_menu.download') }}</li> <li class="menu-option" @click="shareItem" v-if="item">
{{ $t('context_menu.share') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul> </ul>
</div> </div>
</template> </template>
<script> <script>
import {events} from '@/bus'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
@@ -57,10 +83,15 @@
}, },
methods: { methods: {
moveItem() { moveItem() {
// Move item fire popup // Open move item popup
events.$emit('popup:move-item', this.item); events.$emit('popup:open', {name: 'move', item: this.item})
},
shareItem() {
// Open share item popup
events.$emit('popup:open', {name: 'share-create', item: this.item})
}, },
addToFavourites() { addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.app.favourites && ! this.app.favourites.find(el => el.unique_id == this.item.unique_id)) { if (this.app.favourites && ! this.app.favourites.find(el => el.unique_id == this.item.unique_id)) {
this.$store.dispatch('addToFavourites', this.item) this.$store.dispatch('addToFavourites', this.item)
} else { } else {
@@ -1,6 +1,7 @@
<template> <template>
<div id="desktop-toolbar"> <div id="desktop-toolbar">
<div class="toolbar-wrapper"> <div class="toolbar-wrapper">
<!-- Go back--> <!-- Go back-->
<div class="toolbar-go-back" v-if="homeDirectory"> <div class="toolbar-go-back" v-if="homeDirectory">
<div @click="goBack" class="go-back-button"> <div @click="goBack" class="go-back-button">
@@ -20,6 +21,7 @@
<div class="toolbar-button-wrapper"> <div class="toolbar-button-wrapper">
<SearchBar/> <SearchBar/>
</div> </div>
<div class="toolbar-button-wrapper"> <div class="toolbar-button-wrapper">
<ToolbarButtonUpload source="upload" action="Upload file"/> <ToolbarButtonUpload source="upload" action="Upload file"/>
<ToolbarButton <ToolbarButton
@@ -33,6 +35,7 @@
action="Create folder" action="Create folder"
/> />
</div> </div>
<div class="toolbar-button-wrapper"> <div class="toolbar-button-wrapper">
<ToolbarButton <ToolbarButton
:source="preview" :source="preview"
@@ -1,5 +1,5 @@
<template> <template>
<div v-if="fileInfoDetail"> <div class="file-info-content" v-if="fileInfoDetail">
<div class="file-headline" spellcheck="false"> <div class="file-headline" spellcheck="false">
<FilePreview /> <FilePreview />
@@ -7,25 +7,20 @@
<!--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">
<FontAwesomeIcon v-if="fileInfoDetail.type == 'folder'" icon="folder"></FontAwesomeIcon> <FontAwesomeIcon :icon="filePreviewIcon"></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> </div>
<div class="file-info"> <div class="file-info">
<span ref="name" contenteditable="false" class="name">{{ <span ref="name" class="name">{{ fileInfoDetail.name }}</span>
fileInfoDetail.name <span class="mimetype" v-if="fileInfoDetail.mimetype">{{ fileInfoDetail.mimetype }}</span>
}}</span>
<span class="mimetype">{{ fileInfoDetail.mimetype }}</span>
</div> </div>
</div> </div>
</div> </div>
<!--Info list--> <!--Info list-->
<ul class="list-info"> <ul class="list-info">
<!--Filesize--> <!--Filesize-->
<li v-if="fileInfoDetail.filesize" class="list-info-item"> <li v-if="fileInfoDetail.filesize" class="list-info-item">
<b>{{ $t('file_detail.size') }}</b> <b>{{ $t('file_detail.size') }}</b>
@@ -33,7 +28,7 @@
</li> </li>
<!--Latest change--> <!--Latest change-->
<li v-if="fileInfoDetail.created_at" class="list-info-item"> <li class="list-info-item">
<b>{{ $t('file_detail.created_at') }}</b> <b>{{ $t('file_detail.created_at') }}</b>
<span>{{ fileInfoDetail.created_at }}</span> <span>{{ fileInfoDetail.created_at }}</span>
</li> </li>
@@ -46,60 +41,69 @@
<span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span> <span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span>
</div> </div>
</li> </li>
<!--Parent-->
<li v-if="true" class="list-info-item">
<b>Shared</b>
<div class="action-button" @click="shareItemOptions">
<FontAwesomeIcon class="icon" icon="user-edit" />
<span>Can edit and upload files</span>
</div>
<CopyInput class="copy-sharelink" size="small" :value="shareLink" />
</li>
</ul> </ul>
</div> </div>
</template> </template>
<script> <script>
import FilePreview from '@/components/VueFileManagerComponents/FilesView/FilePreview' import FilePreview from '@/components/VueFileManagerComponents/FilesView/FilePreview'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from "@/bus" import {events} from "@/bus"
export default { export default {
name: 'FileInfoPanel', name: 'FileInfoPanel',
components: { components: {
FilePreview FilePreview,
CopyInput,
}, },
computed: { computed: {
...mapGetters(['fileInfoDetail']) ...mapGetters(['fileInfoDetail']),
filePreviewIcon() {
switch (this.fileInfoDetail.type) {
case 'folder':
return 'folder'
break;
case 'file':
return 'file'
break;
case 'image':
return 'file-image'
break;
case 'video':
return 'file-video'
break;
case 'file':
return 'file-audio'
break;
}
}
},
data() {
return {
shareLink: 'http://192.168.1.131:8000/shared?token=3ZlQLIoCR8izoc0PemekHNq3UIMj6OrC0aQ2zowclfjFYa8P6go8fMKPnXTJomvz'
}
}, },
methods: { methods: {
shareItemOptions() {
// Open share item popup
events.$emit('popup:open', {name: 'share-edit', item: this.fileInfoDetail})
},
moveItem() { moveItem() {
// Move item fire popup // Move item fire popup
events.$emit('popup:move-item', this.fileInfoDetail); events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
},
getItemAction() {
// Open image on new tab
if (this.fileInfoDetail.type == 'image') {
this.$openImageOnNewTab(this.fileInfoDetail.file_url)
}
// Download file
if (this.fileInfoDetail.type == 'file') {
this.$downloadFile(
this.fileInfoDetail.file_url,
this.fileInfoDetail.name +
'.' +
this.fileInfoDetail.mimetype
)
} }
// Open folder
if (this.fileInfoDetail.type == 'folder') {
// Todo: open folder
}
},
changeItemName: debounce(function (e) {
// Prevent submit empty string
if (e.target.innerText === '') return
this.$store.dispatch('changeItemName', {
unique_id: this.fileInfoDetail.unique_id,
type: this.fileInfoDetail.type,
name: e.target.innerText
})
}, 300)
} }
} }
</script> </script>
@@ -107,6 +111,10 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import "@assets/app.scss";
.file-info-content {
padding-bottom: 20px;
}
.file-headline { .file-headline {
background: $light_background; background: $light_background;
padding: 12px; padding: 12px;
@@ -156,6 +164,8 @@
.name { .name {
@include font-size(14); @include font-size(14);
font-weight: 700; font-weight: 700;
line-height: 1.4;
display: block;
color: $text; color: $text;
} }
@@ -173,7 +183,7 @@
.list-info-item { .list-info-item {
display: block; display: block;
padding-top: 20px; padding-top: 15px;
&:first-child { &:first-child {
padding-top: 0; padding-top: 0;
@@ -183,9 +193,13 @@
cursor: pointer; cursor: pointer;
.icon { .icon {
@include font-size(11); @include font-size(10);
display: inline-block; display: inline-block;
margin-right: 2px; margin-right: 2px;
path {
fill: $theme_light;
}
} }
} }
@@ -193,6 +207,7 @@
display: block; display: block;
@include font-size(13); @include font-size(13);
color: $theme; color: $theme;
margin-bottom: 2px;
} }
span { span {
@@ -204,6 +219,10 @@
} }
} }
.copy-sharelink {
margin-top: 10px;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.file-headline { .file-headline {
@@ -20,10 +20,11 @@
> >
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item"> <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 }}
}}</span> </span>
<!--Folder thumbnail--> <!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
@@ -38,23 +39,31 @@
<!--Name--> <!--Name-->
<div class="item-name"> <div class="item-name">
<!--Name--> <!--Name-->
<span <b
ref="name" ref="name"
@input="changeItemName" @input="changeItemName"
:contenteditable="!$isMobile()" :contenteditable="!$isMobile()"
class="name" class="name"
>{{ itemName }}</span
> >
{{ itemName }}
</b>
<!--Other attributes--> <div class="item-info">
<span v-if="! isFolder" class="item-size">{{ <!--Shared Icon-->
data.filesize <div class="item-shared" v-if="true">
}}</span> <FontAwesomeIcon class="shared-icon" icon="user-friends"/>
<span class="label">Shared, </span>
</div>
<!--Filesize-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length"> <span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }} {{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}
</span> </span>
</div> </div>
</div>
<span @click.stop="showItemActions" class="show-actions" v-if="$isMobile()"> <span @click.stop="showItemActions" class="show-actions" v-if="$isMobile()">
<FontAwesomeIcon icon="ellipsis-h" class="icon-action"></FontAwesomeIcon> <FontAwesomeIcon icon="ellipsis-h" class="icon-action"></FontAwesomeIcon>
@@ -220,19 +229,29 @@
@include font-size(12); @include font-size(12);
font-weight: 400; font-weight: 400;
color: $text-muted; color: $text-muted;
display: block; display: inline-block;
} }
.name { .item-info {
display: block; display: block;
line-height: 1;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
} }
&[contenteditable='true']:hover { .item-shared {
text-decoration: underline; display: inline-block;
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
.shared-icon {
@include font-size(10);
path {
fill: $theme;
}
} }
} }
@@ -244,6 +263,15 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover {
text-decoration: underline;
}
&.actived { &.actived {
max-height: initial; max-height: initial;
} }
@@ -377,4 +405,6 @@
} }
} }
} }
</style> </style>
@@ -20,9 +20,9 @@
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item"> <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 }}
}}</span> </span>
<!--Folder thumbnail--> <!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/> <FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
@@ -37,20 +37,32 @@
<!--Name--> <!--Name-->
<div class="item-name"> <div class="item-name">
<!--Name--> <!--Name-->
<span <b
ref="name" ref="name"
@input="changeItemName" @input="changeItemName"
:contenteditable="!$isMobile() && !$isTrashLocation()" :contenteditable="!$isMobile() && !$isTrashLocation()"
class="name" class="name"
>{{ itemName }}</span> >
{{ itemName }}
</b>
<!--Other attributes--> <div class="item-info">
<!--Shared Icon-->
<div class="item-shared" v-if="true">
<FontAwesomeIcon class="shared-icon" icon="user-friends"/>
<span class="label">Shared,</span>
</div>
<!--Filesize and timestamp-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span> <span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<!--Folder item counts-->
<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 }}
</span> </span>
</div> </div>
</div>
<!--Go Next icon--> <!--Go Next icon-->
<div class="actions" v-if="$isMobile()"> <div class="actions" v-if="$isMobile()">
@@ -233,12 +245,34 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
.item-info {
display: block;
line-height: 1;
}
.item-shared {
display: inline-block;
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
.shared-icon {
@include font-size(10);
path {
fill: $theme;
}
}
}
.item-size, .item-size,
.item-length { .item-length {
@include font-size(12); @include font-size(12);
font-weight: 400; font-weight: 400;
color: $text-muted; color: $text-muted;
display: block;
} }
.name { .name {
@@ -8,46 +8,38 @@
@click="closeAndResetContextMenu" @click="closeAndResetContextMenu"
> >
<div class="menu-wrapper"> <div class="menu-wrapper">
<ul class="menu-options">
<li class="menu-option"
@click="addToFavourites"
v-if="! $isTrashLocation() && fileInfoDetail && isFolder"
>
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" <!--Mobile for trash location-->
@click="$store.dispatch('restoreItem', fileInfoDetail)" <ul v-if="$isTrashLocation()" class="menu-options">
v-if="fileInfoDetail && $isTrashLocation()" <li class="menu-option" @click="$store.dispatch('restoreItem', fileInfoDetail)" v-if="fileInfoDetail">
>
{{ $t('context_menu.restore') }} {{ $t('context_menu.restore') }}
</li> </li>
<li <li class="menu-option" @click="downloadItem" v-if="! isFolder">
class="menu-option"
@click="renameItem"
v-if="fileInfoDetail"
>
{{ $t('context_menu.rename') }}
</li>
<li
class="menu-option"
@click="moveItem"
v-if="fileInfoDetail"
>
{{ $t('context_menu.move') }}
</li>
<li
class="menu-option"
@click="downloadItem"
v-if="! isFolder"
>
{{ $t('context_menu.download') }} {{ $t('context_menu.download') }}
</li> </li>
<li <li class="menu-option delete" @click="removeItem" v-if="fileInfoDetail">
class="menu-option delete" {{ $t('context_menu.delete') }}
@click="removeItem" </li>
v-if="fileInfoDetail" </ul>
>
<!--Mobile for Base location-->
<ul v-if="$isBaseLocation()" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
{{ $t('context_menu.rename') }}
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail">
{{ $t('context_menu.move') }}
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail">
{{ $t('context_menu.share') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
<li class="menu-option delete" @click="removeItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }} {{ $t('context_menu.delete') }}
</li> </li>
</ul> </ul>
@@ -92,8 +84,13 @@
}, },
methods: { methods: {
moveItem() { moveItem() {
// Move item fire popup // Open move item popup
events.$emit('popup:move-item', this.fileInfoDetail); events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
},
shareItem() {
// Open share item popup
events.$emit('popup:open', {name: 'share-create', item: this.fileInfoDetail})
}, },
addToFavourites() { addToFavourites() {
if (this.app.favourites && !this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)) { if (this.app.favourites && !this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)) {
@@ -0,0 +1,44 @@
<template>
<div class="action-button">
<FontAwesomeIcon class="icon" :icon="icon" />
<span class="label">
<slot></slot>
</span>
</div>
</template>
<script>
export default {
name: 'ActionButton',
props: ['icon'],
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.action-button {
cursor: pointer;
.label {
@include font-size(12);
color: $theme_light;
font-weight: 600;
text-decoration: underline;
}
.icon {
@include font-size(10);
display: inline-block;
margin-right: 2px;
path {
fill: $theme_light;
}
}
}
@media (prefers-color-scheme: dark) {
}
</style>
@@ -0,0 +1,78 @@
<template>
<div class="inline-wrapper icon-append copy-input" :class="size" @click="copyUrl">
<input ref="sel" :value="value" id="link-input" type="text" class="input-text" readonly>
<div class="icon">
<FontAwesomeIcon :icon="isCopiedLink ? 'check' : 'link'"/>
</div>
</div>
</template>
<script>
export default {
name: 'CopyInput',
props: ['size', 'value'],
data() {
return {
isCopiedLink: false,
}
},
methods: {
copyUrl() {
// Get input value
var copyText = document.getElementById("link-input");
// select link
copyText.select();
copyText.setSelectionRange(0, 99999);
// Copy
document.execCommand("copy");
// Mark button as copied
this.isCopiedLink = true
// Reset copy button
setTimeout(() => {this.isCopiedLink = false}, 1000)
},
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
// Single page
.copy-input {
&.small {
&.icon-append {
.icon {
padding: 8px 10px;
@include font-size(11);
}
}
input {
padding: 6px 10px;
@include font-size(13);
}
}
.icon {
cursor: pointer;
}
input {
text-overflow: ellipsis;
&:disabled {
color: $text;
cursor: pointer;
}
}
}
</style>
@@ -0,0 +1,194 @@
<template>
<div class="select">
<!--Area-->
<div class="input-area" :class="{'is-active': isOpen, 'is-error': isError}" @click="openMenu">
<!--If is selected-->
<div class="selected" v-if="selected">
<div class="option-icon" v-if="selected.icon">
<FontAwesomeIcon :icon="selected.icon" />
</div>
<span class="option-value">{{ selected.label }}</span>
</div>
<!--If is empty-->
<div class="not-selected" v-if="! selected">
<span class="option-value placehoder">Selected your permision</span>
</div>
<FontAwesomeIcon icon="chevron-down" class="chevron"/>
</div>
<!--Options-->
<transition name="slide-in">
<ul class="input-options" v-if="isOpen">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in options" :key="i">
<div class="option-icon" v-if="option.icon">
<FontAwesomeIcon :icon="option.icon" />
</div>
<span class="option-value">{{ option.label }}</span>
</li>
</ul>
</transition>
</div>
</template>
<script>
export default {
name:'SelectInput',
props: ['options', 'isError', 'default'],
data() {
return {
selected: undefined,
isOpen: false,
}
},
methods: {
selectOption(option) {
// Emit selected
this.$emit('input', option.value)
// Get selected
this.selected = option
// Close menu
this.isOpen = false
},
openMenu() {
this.isOpen = ! this.isOpen
},
},
created() {
if (this.default)
this.selected = this.options.find(option => option.value === this.default)
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.select {
position: relative;
user-select: none;
}
.input-options {
background: $light_background;
border-radius: 8px;
position: absolute;
overflow: hidden;
top: 65px;
left: 0;
right: 0;
z-index: 9;
.option-item {
padding: 13px 20px;
display: block;
border-bottom: 1px solid #EBEBEB;
cursor: pointer;
&:hover {
color: $theme;
background: rgba($theme, .1);
}
&:last-child {
border-bottom: none;
}
}
}
.input-area {
justify-content: space-between;
background: $light_background;
border: 1px solid transparent;
@include transition(150ms);
align-items: center;
border-radius: 8px;
padding: 13px 20px;
display: flex;
outline: 0;
width: 100%;
cursor: pointer;
.chevron {
@include transition(150ms);
}
&.is-active {
border-color: $theme;
box-shadow: 0 0 7px rgba($theme, 0.3);
.chevron {
@include transform(rotate(180deg));
}
}
&.is-error {
border-color: $danger;
box-shadow: 0 0 7px rgba($danger, 0.3);
}
}
.option-icon {
width: 20px;
display: inline-block;
@include font-size(12);
}
.option-value {
@include font-size(15);
font-weight: 700;
width: 100%;
&.placehoder {
color: $light_text;
}
}
.slide-in-enter-active {
transition: all 150ms ease;
}
.slide-in-enter /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateY(-50px);
}
@media (prefers-color-scheme: dark) {
.input-area {
background: $dark_mode_foreground;
.option-icon {
path {
fill: $theme
}
}
}
.input-options {
background: $dark_mode_foreground;
.option-item {
border-bottom: none;
&:hover {
color: $theme;
background: rgba($theme, .1);
}
&:last-child {
border-bottom: none;
}
}
}
}
</style>
@@ -0,0 +1,93 @@
<template>
<div class="input-wrapper">
<div class="switch-content">
<label class="input-label" v-if="label">{{ label }}:</label>
<small class="input-info" v-if="info">{{ info }}</small>
</div>
<div class="switch-content text-right">
<div
class="switch"
:class="{ active: isSwitched }"
@click="changeState"
>
<div class="switch-button"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'SwitchInput',
props: ['label', 'name', 'state', 'info'],
data() {
return {
isSwitched: undefined
}
},
methods: {
changeState() {
this.isSwitched = ! this.isSwitched
this.$emit('input', this.isSwitched)
}
},
mounted() {
this.isSwitched = this.state
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.input-wrapper {
display: flex;
width: 100%;
.input-label {
color: $text;
}
.switch-content {
width: 100%;
&:last-child {
width: 80px;
}
}
}
.switch {
width: 50px;
height: 28px;
border-radius: 50px;
display: block;
background: #f1f1f5;
position: relative;
@include transition;
.switch-button {
@include transition;
width: 22px;
height: 22px;
border-radius: 50px;
display: block;
background: white;
position: absolute;
top: 3px;
left: 3px;
box-shadow: 0 2px 4px rgba(37, 38, 94, 0.1);
cursor: pointer;
}
&.active {
background: $theme;
.switch-button {
left: 25px;
}
}
}
</style>
@@ -0,0 +1,132 @@
<template>
<PopupWrapper name="move">
<!--Title-->
<PopupHeader :title="$t('popup_move_item.title')" />
<!--Content-->
<PopupContent type="height-limited" v-if="app && pickedItem">
<!--Show Spinner when loading folders-->
<Spinner v-if="isLoadingTree"/>
<!--Folder tree-->
<div v-if="! isLoadingTree">
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="location"/>
<TreeMenu :depth="1" :nodes="items" v-for="items in app.folders" :key="items.unique_id"/>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="popup-button"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="moveItem"
:button-style="selectedFolder ? 'theme' : 'secondary'"
>{{ $t('popup_move_item.submit') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import Spinner from '@/components/VueFileManagerComponents/FilesView/Spinner'
import TreeMenu from '@/components/VueFileManagerComponents/Others/TreeMenu'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MoveItem',
components: {
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters(['app']),
},
data() {
return {
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
}
},
methods: {
moveItem() {
// Prevent empty submit
if (! this.selectedFolder) return
// Move item
this.$store.dispatch('moveItem', [this.pickedItem, this.selectedFolder])
// Close popup
events.$emit('popup:close')
},
},
mounted() {
// Select folder in tree
events.$on('pick-folder', folder => {
if (folder.unique_id === this.pickedItem.unique_id) {
this.selectedFolder = undefined
} else {
this.selectedFolder = folder
}
})
// Show Move item popup
events.$on('popup:open', args => {
if (args.name !== 'move') return
// Show tree spinner
this.isLoadingTree = true
// Get folder tree and hide spinner
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
})
// Store picked item
this.pickedItem = args.item
})
// Close popup
events.$on('popup:close', () => {
// Clear selected folder
setTimeout(() => {
this.selectedFolder = undefined
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.item-thumbnail {
margin-bottom: 20px;
}
</style>
@@ -0,0 +1,40 @@
<template>
<div class="actions">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'PopupActions',
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.actions {
padding: 20px;
margin: 0 -10px;
display: flex;
.popup-button {
width: 100%;
margin: 0 10px;
}
}
.small {
.actions {
padding: 15px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}
@media (prefers-color-scheme: dark) {
}
</style>
@@ -0,0 +1,62 @@
<template>
<div class="popup-content" :class="type">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'PopupContent',
props: [
'type'
]
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup-content {
&.height-limited {
height: 400px;
overflow-y: auto;
}
}
.small {
.popup-content {
top: 57px;
bottom: 72px;
position: absolute;
left: 0;
right: 0;
height: initial;
}
}
@media (prefers-color-scheme: dark) {
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
</style>
@@ -0,0 +1,52 @@
<template>
<div class="popup-header">
<h1 class="title">{{ title }}</h1>
</div>
</template>
<script>
export default {
name: 'PopupHeader',
props: [
'title'
]
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup-header {
padding: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.message {
@include font-size(16);
color: #8b8f9a;
margin-top: 5px;
}
}
.small {
.popup-header {
padding: 15px;
}
}
@media (prefers-color-scheme: dark) {
.popup-header {
.title {
color: $dark_mode_text_primary;
}
.message {
color: $dark_mode_text_secondary;
}
}
}
</style>
@@ -0,0 +1,142 @@
<template>
<transition name="popup">
<div class="popup" @click.self="closePopup" v-show="isVisibleWrapper">
<div class="popup-wrapper">
<slot></slot>
</div>
</div>
</transition>
</template>
<script>
import {events} from '@/bus'
export default {
name: 'PopupWrapper',
props: [
'name'
],
data() {
return {
isVisibleWrapper: false,
}
},
methods: {
closePopup() {
events.$emit('popup:close')
}
},
created() {
// Open called popup
events.$on('popup:open', ({name}) => {
if (this.name === name) this.isVisibleWrapper = true
})
// Close popup
events.$on('popup:close', () => {
// Close popup
this.isVisibleWrapper = false
})
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
overflow-y: auto;
display: grid;
padding: 40px;
}
.popup-wrapper {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
background: white;
margin: auto;
width: 480px;
z-index: 12;
}
// Desktop, tablet
.medium, .large {
// Animations
.popup-enter-active {
animation: popup-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-in 0.15s ease reverse;
}
}
.small {
.popup {
overflow: hidden;
}
.popup-wrapper {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(0) scale(1);
box-shadow: none;
width: 100%;
border-radius: 0px;
}
// Animations
.popup-enter-active {
animation: popup-slide-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
@media (prefers-color-scheme: dark) {
.popup-wrapper {
background: $dark_mode_background;
box-shadow: $dark_mode_popup_shadow;
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.popup-wrapper {
background: $dark_mode_background;
}
}
</style>
@@ -1,299 +0,0 @@
<template>
<transition name="popup">
<div class="popup" @click.self="closePopup" v-if="isVisibleWrapper">
<div class="popup-wrapper">
<!--Title-->
<div class="popup-header">
<h1 class="title">{{ $t('popup_move_item.title') }}</h1>
<!--<p v-if="message" class="message">{{ message }}</p>-->
</div>
<!--Content-->
<div class="popup-content" v-if="app && pickedItem">
<Spinner v-if="isLoadingTree"/>
<div v-if="! isLoadingTree">
<ThumbnailItem class="item-thumbnail" :file="pickedItem"/>
<TreeMenu :depth="1" :nodes="items" v-for="items in app.folders" :key="items.unique_id"/>
</div>
</div>
<!--Actions-->
<div class="actions">
<ButtonBase
class="popup-button"
@click.native="closePopup"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="moveItem"
:button-style="selectedFolder ? 'theme' : 'secondary'"
>{{ $t('popup_move_item.submit') }}
</ButtonBase>
</div>
</div>
</div>
</transition>
</template>
<script>
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import Spinner from '@/components/VueFileManagerComponents/FilesView/Spinner'
import TreeMenu from '@/components/VueFileManagerComponents/Others/TreeMenu'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'PopupMoveItem',
components: {
ThumbnailItem,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters(['app']),
},
data() {
return {
isVisibleWrapper: false,
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
}
},
methods: {
moveItem() {
// Prevent empty submit
if (! this.selectedFolder) return
// Move item
this.$store.dispatch('moveItem', [this.pickedItem, this.selectedFolder])
// Close popup
events.$emit('popup:close')
},
closePopup() {
events.$emit('popup:close')
}
},
mounted() {
// Select folder in tree
events.$on('pick-folder', folder => {
if (folder.unique_id == this.pickedItem.unique_id) {
this.selectedFolder = undefined
} else {
this.selectedFolder = folder
}
})
// Show popup
events.$on('popup:move-item', item => {
// Show tree spinner
this.isLoadingTree = true
// Get folder tree and hide spinner
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
}).catch(() => {
this.isLoadingTree = false
})
// Make popup visible
this.isVisibleWrapper = true
// Store picked item
this.pickedItem = item
})
// Close popup
events.$on('popup:close', () => {
// Hide popup wrapper
this.isVisibleWrapper = false
// Clear selected folder
this.selectedFolder = undefined
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.popup {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
overflow-y: auto;
display: grid;
padding: 40px;
}
.popup-wrapper {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
background: white;
margin: auto;
width: 480px;
z-index: 12;
}
.popup-header {
padding: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.message {
@include font-size(16);
color: #8b8f9a;
margin-top: 5px;
}
}
.popup-content {
height: 400px;
overflow-y: auto;
}
.item-thumbnail {
margin-bottom: 20px;
}
.actions {
padding: 20px;
margin: 0 -10px;
display: flex;
.popup-button {
width: 100%;
margin: 0 10px;
}
}
// Desktop, tablet
.medium, .large {
// Animations
.popup-enter-active {
animation: popup-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-in 0.15s ease reverse;
}
}
// Mobile styles
.small {
.popup {
overflow: hidden;
}
.popup-wrapper {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(0) scale(1);
box-shadow: none;
width: 100%;
border-radius: 0px;
.popup-content {
top: 57px;
bottom: 72px;
position: absolute;
left: 0;
right: 0;
height: initial;
}
.actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}
.popup-header {
padding: 15px;
}
.actions {
padding: 15px;
}
// Animations
.popup-enter-active {
animation: popup-slide-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
// Dark mode
@media (prefers-color-scheme: dark) {
.popup-wrapper {
background: $dark_mode_background;
box-shadow: $dark_mode_popup_shadow;
}
.popup-header {
.title {
color: $dark_mode_text_primary;
}
.message {
color: $dark_mode_text_secondary;
}
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.popup-wrapper {
background: $dark_mode_background;
}
}
</style>
@@ -0,0 +1,216 @@
<template>
<PopupWrapper name="share-create">
<!--Title-->
<PopupHeader :title="'Share Your ' + itemTypeTitle" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<!--Form to set sharing-->
<ValidationObserver v-if="! isGeneratedShared" ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<!--Permision Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" class="input-wrapper" name="Permission" rules="required" v-slot="{ errors }">
<label class="input-label">Permission:</label>
<SelectInput v-model="shareOptions.permission" :options="permissionOptions" :isError="errors[0]"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--Password Switch-->
<div class="input-wrapper">
<div class="inline-wrapper">
<label class="input-label">Password Protected:</label>
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="0"/>
</div>
</div>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isPassword" tag="div" mode="passive" class="input-wrapper password" name="Password" rules="required" v-slot="{ errors }">
<input v-model="shareOptions.password" :class="{'is-error': errors[0]}" type="text" placeholder="Type your password">
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<!--Copy generated link-->
<div v-if="isGeneratedShared" class="form-wrapper">
<div class="input-wrapper">
<label class="input-label">Share url:</label>
<CopyInput size="small" :value="shareLink" />
</div>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
v-if="! isGeneratedShared"
class="popup-button"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="submitShareOptions"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>{{ submitButtonText }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import SwitchInput from '@/components/VueFileManagerComponents/Others/Forms/SwitchInput'
import SelectInput from '@/components/VueFileManagerComponents/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import axios from 'axios'
export default {
name: 'ShareCreate',
components: {
ValidationProvider,
ValidationObserver,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
},
computed: {
...mapGetters(['app']),
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? 'Folder' : 'File'
},
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
submitButtonText() {
return this.isGeneratedShared ? 'Awesome, Im done!' : 'Generate Link'
}
},
data() {
return {
shareOptions: {
isPassword: false,
password: undefined,
permission: undefined,
},
permissionOptions: [
{
label: 'Can edit and upload files',
value: 'editor',
icon: 'user-edit',
},
{
label: 'Can only view and download',
value: 'visitor',
icon: 'user',
},
],
pickedItem: undefined,
shareLink: undefined,
isGeneratedShared: false,
isLoading: false,
}
},
methods: {
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return;
}
// Validate fields
const isValid = await this.$refs.shareForm.validate();
if (!isValid) return;
this.isLoading = true
// Send request to get share link
axios
.post('/api/share/generate', this.shareOptions)
.then(response => {
// End loading
this.isLoading = false
this.shareLink = response.data
this.isGeneratedShared = true
})
.catch(error => {
// todo: catch errors
// End loading
this.isLoading = false
})
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'share-create') return
// Store picked item
this.pickedItem = args.item
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.isGeneratedShared = false
this.shareLink = undefined
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
}
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
.input-wrapper {
&.password {
margin-top: -10px;
}
}
.item-thumbnail {
margin-bottom: 20px;
}
</style>
@@ -0,0 +1,237 @@
<template>
<PopupWrapper name="share-edit">
<!--Title-->
<PopupHeader title="Update sharing options" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<!--Form to set sharing-->
<ValidationObserver ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<!--Share link-->
<div class="input-wrapper">
<label class="input-label">Share url:</label>
<CopyInput size="small" :value="shareLink" />
</div>
<!--Permision Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" class="input-wrapper" name="Permission" rules="required" v-slot="{ errors }">
<label class="input-label">Permission:</label>
<SelectInput v-model="shareOptions.permission" :options="permissionOptions" default="visitor" :isError="errors[0]"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--Password Switch-->
<div class="input-wrapper">
<div class="inline-wrapper">
<label class="input-label">Password Protected:</label>
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="0"/>
</div>
<ActionButton @click.native="changePassword" v-if="isPasswordChangeButton" icon="pencil-alt">Change Password</ActionButton>
</div>
<!--Set password-->
<ValidationProvider v-if="isPasswordInput || shareOptions.isPassword" tag="div" mode="passive" class="input-wrapper password" name="Password" rules="required" v-slot="{ errors }">
<input v-model="shareOptions.password" :class="{'is-error': errors[0]}" type="text" placeholder="Type your password">
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="popup-button"
@click.native="destroySharing"
:button-style="destroyButtonStyle"
>{{ destroyButtonText }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="submitShareOptions"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>Save Changes
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import SwitchInput from '@/components/VueFileManagerComponents/Others/Forms/SwitchInput'
import SelectInput from '@/components/VueFileManagerComponents/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ActionButton from '@/components/VueFileManagerComponents/Others/ActionButton'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import axios from 'axios'
export default {
name: 'ShareEdit',
components: {
ValidationProvider,
ValidationObserver,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
},
computed: {
...mapGetters(['app']),
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
destroyButtonText() {
return this.isConfirmedDestroy ? 'Confirm' : 'Stop Sharing'
},
destroyButtonStyle() {
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
}
},
data() {
return {
shareOptions: {
isPassword: false,
password: undefined,
permission: undefined,
},
permissionOptions: [
{
label: 'Can edit and upload files',
value: 'editor',
icon: 'user-edit',
},
{
label: 'Can only view and download',
value: 'visitor',
icon: 'user',
},
],
pickedItem: undefined,
isLoading: false,
isPasswordInput: false,
isPasswordChangeButton: true,
isConfirmedDestroy: false,
shareLink: 'http://192.168.1.131:8000/shared?token=3ZlQLIoCR8izoc0PemekHNq3UIMj6OrC0aQ2zowclfjFYa8P6go8fMKPnXTJomvz'
}
},
methods: {
changePassword() {
this.isPasswordInput = true
this.isPasswordChangeButton = false
},
destroySharing() {
if (! this.isConfirmedDestroy) {
this.isConfirmedDestroy = true
return
} else {
this.$closePopup()
}
},
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return;
}
// Validate fields
const isValid = await this.$refs.shareForm.validate();
if (!isValid) return;
this.isLoading = true
// Send request to get share link
axios
.post('/api/share/generate', this.shareOptions)
.then(response => {
// End loading
this.isLoading = false
this.shareLink = response.data
this.isGeneratedShared = true
})
.catch(error => {
// todo: catch errors
// End loading
this.isLoading = false
})
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'share-edit') return
// Store picked item
this.pickedItem = args.item
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.isConfirmedDestroy = false
this.isPasswordInput = false
this.isPasswordChangeButton = true
//this.shareLink = undefined
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
}
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
.input-wrapper {
&.password {
margin-top: -10px;
}
}
.item-thumbnail {
margin-bottom: 20px;
}
</style>
@@ -24,7 +24,7 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.text-label { .text-label {
color: rgba($dark_mode_text_secondary, .4); color: $theme;
} }
} }
</style> </style>
@@ -1,17 +1,17 @@
<template> <template>
<div class="file-item"> <div class="file-item" v-if="item">
<!--Thumbnail for item--> <!--Thumbnail for item-->
<div class="icon-item"> <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">{{ item.mimetype }}</span>
<!--Folder thumbnail--> <!--Folder thumbnail-->
<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" class="image" :src="file.thumbnail" :alt="file.name"/> <img v-if="isImage" class="image" :src="item.thumbnail" :alt="item.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"/>
@@ -21,11 +21,21 @@
<div class="item-name"> <div class="item-name">
<!--Name--> <!--Name-->
<span class="name">{{ file.name }}</span> <span class="name">{{ item.name }}</span>
<!--Other attributes--> <div v-if="info === 'location'">
<span class="subtitle">{{ $t('item_thumbnail.original_location') }}: {{ currentFolder.name }}</span> <span class="subtitle">{{ $t('item_thumbnail.original_location') }}: {{ currentFolder.name }}</span>
</div> </div>
<div v-if="info === 'metadata'">
<span v-if="! isFolder" class="item-size">{{ item.filesize }}, {{ item.created_at }}</span>
<span v-if="isFolder" class="item-length">
{{ item.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', item.items) }}, {{ item.created_at }}
</span>
</div>
</div>
</div> </div>
</template> </template>
@@ -34,18 +44,18 @@
export default { export default {
name: 'ThumbnailItem', name: 'ThumbnailItem',
props: ['file'], props: ['item', 'info'],
computed: { computed: {
...mapGetters(['currentFolder']), ...mapGetters(['currentFolder']),
isFolder() { isFolder() {
return this.file.type === 'folder' return this.item.type === 'folder'
}, },
isFile() { isFile() {
return this.file.type !== 'folder' && this.file.type !== 'image' return this.item.type !== 'folder' && this.item.type !== 'image'
}, },
isImage() { isImage() {
return this.file.type === 'image' return this.item.type === 'image'
} },
}, },
} }
</script> </script>
@@ -66,6 +76,14 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
.item-size,
.item-length {
@include font-size(12);
font-weight: 400;
color: $text-muted;
display: block;
}
.subtitle { .subtitle {
@include font-size(11); @include font-size(11);
font-weight: 400; font-weight: 400;
@@ -141,7 +159,7 @@
.small { .small {
.file-item { .file-item {
padding: 0 15px; padding: 0 15px;
margin-bottom: 10px; margin-bottom: 15px;
} }
} }
@@ -26,7 +26,7 @@
events.$on('popup:close', () => this.isVisibleVignette = false) events.$on('popup:close', () => this.isVisibleVignette = false)
// Show vignette // Show vignette
events.$on('popup:move-item', () => this.isVisibleVignette = true) events.$on('popup:open', () => this.isVisibleVignette = true)
events.$on('alert:open', () => this.isVisibleVignette = true) events.$on('alert:open', () => this.isVisibleVignette = true)
events.$on('success:open', () => this.isVisibleVignette = true) events.$on('success:open', () => this.isVisibleVignette = true)
} }
@@ -16,10 +16,10 @@
<FontAwesomeIcon class="icon" icon="hdd"/> <FontAwesomeIcon class="icon" icon="hdd"/>
<span class="label">{{ $t('locations.home') }}</span> <span class="label">{{ $t('locations.home') }}</span>
</li> </li>
<!--<li class="menu-list-item"> <li class="menu-list-item">
<FontAwesomeIcon class="icon" icon="share"/> <FontAwesomeIcon class="icon" icon="share"/>
<span class="label">Shared</span> <span class="label">Shared</span>
</li>--> </li>
<li class="menu-list-item" @click="getTrash"> <li class="menu-list-item" @click="getTrash">
<FontAwesomeIcon class="icon" icon="trash-alt"/> <FontAwesomeIcon class="icon" icon="trash-alt"/>
<span class="label">{{ $t('locations.trash') }}</span> <span class="label">{{ $t('locations.trash') }}</span>
@@ -11,7 +11,7 @@
</div> </div>
<transition name="user-menu"> <transition name="user-menu">
<div class="user-menu" v-if="isOpenedMenu"> <div class="user-menu" v-if="isOpenedMenu">
<ul class="menu-options" id="menu-options-list" @click="closeMenu"> <ul class="menu-options" @click="closeMenu">
<li class="menu-option"> <li class="menu-option">
<router-link :to="{name: 'Profile'}"> <router-link :to="{name: 'Profile'}">
{{ $t('context_menu.profile_settings') }} {{ $t('context_menu.profile_settings') }}
@@ -176,7 +176,8 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.user-headline { .user-headline {
background: $dark_mode_background; background: $dark_mode_foreground;
padding: 0;
&:hover { &:hover {
background: transparent; background: transparent;
+8 -1
View File
@@ -209,11 +209,18 @@ const Helpers = {
anchor.click() anchor.click()
} }
Vue.prototype.$isTrashLocation = function() { Vue.prototype.$closePopup = function() {
events.$emit('popup:close')
}
Vue.prototype.$isTrashLocation = function() {
return store.getters.currentFolder && store.getters.currentFolder.location === 'trash' || store.getters.currentFolder && store.getters.currentFolder.location === 'trash-root' ? true : false return store.getters.currentFolder && store.getters.currentFolder.location === 'trash' || store.getters.currentFolder && store.getters.currentFolder.location === 'trash-root' ? true : false
} }
Vue.prototype.$isBaseLocation = function() {
return store.getters.currentFolder && store.getters.currentFolder.location === 'base' ? true : false
}
Vue.prototype.$isMobile = function() { Vue.prototype.$isMobile = function() {
const toMatch = [ const toMatch = [
/Android/i, /Android/i,
+1
View File
@@ -125,6 +125,7 @@
"detail": "Detail", "detail": "Detail",
"rename": "Rename", "rename": "Rename",
"delete": "Delete", "delete": "Delete",
"share": "Share",
"move": "Move" "move": "Move"
}, },
"sidebar": { "sidebar": {
+1
View File
@@ -125,6 +125,7 @@
"detail": "Detail", "detail": "Detail",
"rename": "Premenovať", "rename": "Premenovať",
"delete": "Vymazať", "delete": "Vymazať",
"share": "Zdieľať",
"move": "Presunúť" "move": "Presunúť"
}, },
"sidebar": { "sidebar": {
+12
View File
@@ -9,6 +9,11 @@ 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 {
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio, faFileAudio,
faFileVideo, faFileVideo,
faSyncAlt, faSyncAlt,
@@ -20,6 +25,7 @@ import {
faEllipsisV, faEllipsisV,
faChevronLeft, faChevronLeft,
faChevronRight, faChevronRight,
faChevronDown,
faUpload, faUpload,
faFolderPlus, faFolderPlus,
faTh, faTh,
@@ -37,6 +43,11 @@ import {
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio, faFileAudio,
faFileVideo, faFileVideo,
faHdd, faHdd,
@@ -49,6 +60,7 @@ library.add(
faEllipsisV, faEllipsisV,
faChevronLeft, faChevronLeft,
faChevronRight, faChevronRight,
faChevronDown,
faUpload, faUpload,
faTrashAlt, faTrashAlt,
faFolderPlus, faFolderPlus,
+20 -1
View File
@@ -4,6 +4,8 @@ import store from '@/store'
import Index from './views/Auth/SignIn' import Index from './views/Auth/SignIn'
import SignUp from './views/Auth/SignUp' import SignUp from './views/Auth/SignUp'
import SharedContent from './views/Shared/SharedContent'
import VerifyByPassword from './views/Shared/VerifyByPassword'
import ForgottenPassword from './views/Auth/ForgottenPassword' import ForgottenPassword from './views/Auth/ForgottenPassword'
import CreateNewPassword from './views/Auth/CreateNewPassword' import CreateNewPassword from './views/Auth/CreateNewPassword'
@@ -47,6 +49,22 @@ const router = new Router({
requiresAuth: false requiresAuth: false
}, },
}, },
{
name: 'SharedContent',
path: '/shared',
component: SharedContent,
meta: {
requiresAuth: false
},
},
{
name: 'VerifyByPassword',
path: '/protected',
component: VerifyByPassword,
meta: {
requiresAuth: false
},
},
{ {
name: 'Files', name: 'Files',
path: '/files', path: '/files',
@@ -80,7 +98,8 @@ router.beforeEach((to, from, next) => {
// this route requires auth, check if logged in // this route requires auth, check if logged in
// if not, redirect to login page. // if not, redirect to login page.
if ( ! store.getters.isLogged || ! config.hasAuthCookie) { //if ( ! store.getters.isLogged) {
if ( false ) {
next({ next({
name: 'SignIn', name: 'SignIn',
query: { redirect: to.fullPath } query: { redirect: to.fullPath }
+3 -2
View File
@@ -1,7 +1,8 @@
import axios from 'axios' import axios from 'axios'
import {events} from '@/bus' import {events} from '@/bus'
import i18n from '@/i18n/index.js'
import router from '@/router' import router from '@/router'
import { includes } from 'lodash'
import i18n from '@/i18n/index.js'
const defaultState = { const defaultState = {
fileInfoPanelVisible: localStorage.getItem('file_info_visibility') == 'true' || false, fileInfoPanelVisible: localStorage.getItem('file_info_visibility') == 'true' || false,
@@ -23,7 +24,7 @@ const actions = {
events.$emit('show:content') events.$emit('show:content')
// Go to files view // Go to files view
if (router.currentRoute.name !== 'Files') { if ( ! includes(['Files', 'SharedContent'], router.currentRoute.name) ) {
router.push({name: 'Files'}) router.push({name: 'Files'})
} }
+6
View File
@@ -203,6 +203,12 @@
width: 100%; width: 100%;
} }
} }
.item-shared {
.label {
display: none;
}
}
} }
&.compact-scale { &.compact-scale {
+255
View File
@@ -0,0 +1,255 @@
<template>
<div @click="fileViewClick" @contextmenu.prevent.capture="contextMenu($event, undefined)" id="files-view" :class="filesViewWidth">
<ContextMenu />
<DesktopToolbar v-if="! $isMinimalScale()"/>
<FilesContainer/>
</div>
</template>
<script>
import UploadProgress from '@/components/VueFileManagerComponents/FilesView/UploadProgress'
import FilesContainer from '@/components/VueFileManagerComponents/FilesView/FilesContainer'
import DesktopToolbar from '@/components/VueFileManagerComponents/FilesView/DesktopToolbar'
import ContextMenu from '@/components/VueFileManagerComponents/FilesView/ContextMenu'
import {ResizeSensor} from 'css-element-queries'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilesView',
components: {
UploadProgress,
FilesContainer,
DesktopToolbar,
ContextMenu,
},
computed: {
...mapGetters(['config', 'filesViewWidth']),
},
methods: {
fileViewClick() {
events.$emit('contextMenu:hide')
},
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
},
handleContentResize() {
let filesView = document.getElementById('files-view')
.clientWidth
if (filesView >= 0 && filesView <= 690)
this.$store.commit('SET_FILES_VIEW_SIZE', 'minimal-scale')
else if (filesView >= 690 && filesView <= 960)
this.$store.commit('SET_FILES_VIEW_SIZE', 'compact-scale')
else if (filesView >= 960)
this.$store.commit('SET_FILES_VIEW_SIZE', 'full-scale')
},
},
mounted() {
var filesView = document.getElementById('files-view');
new ResizeSensor(filesView, this.handleContentResize);
let homeDirectory = {
unique_id: 0,
name: 'Home',
location: 'base',
}
// Set start directory
this.$store.commit('SET_START_DIRECTORY', homeDirectory)
// Load folder
this.$store.dispatch('goToFolder', [homeDirectory, false, true])
}
}
</script>
<style lang="scss">
@import "@assets/app.scss";
#files-view {
font-family: 'Nunito', sans-serif;
font-size: 16px;
//overflow: hidden;
width: 100%;
height: 100%;
position: relative;
min-width: 320px;
overflow-x: hidden;
&.minimal-scale {
padding: 0;
.mobile-toolbar {
padding: 10px 0 5px;
}
.popup-wrapper {
left: 15px;
right: 15px;
padding: 25px 15px;
}
.toolbar {
display: block;
position: sticky;
top: 0;
}
.toolbar-go-back {
padding-top: 15px;
}
.toolbar-tools {
text-align: left;
display: flex;
.toolbar-button-wrapper {
width: 100%;
&:last-child {
text-align: right;
}
}
}
.files-container {
padding-left: 15px;
padding-right: 15px;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
overflow-y: auto;
.file-list {
//height: 100%;
&.grid {
grid-template-columns: repeat(auto-fill, 120px);
.file-wrapper {
.file-item {
width: 120px;
}
.icon-item {
margin-bottom: 10px;
height: 90px;
.file-icon {
@include font-size(75);
}
.file-icon-text {
@include font-size(12);
}
.folder-icon {
@include font-size(75);
margin-top: 0;
margin-bottom: 0;
}
.image {
width: 90px;
height: 90px;
}
}
.item-name .name {
@include font-size(13);
line-height: 1.2;
max-height: 30px;
}
}
}
}
}
.file-wrapper {
.item-name .name {
max-width: 220px;
}
}
.search-bar {
input {
min-width: initial;
width: 100%;
}
}
.item-shared {
.label {
display: none;
}
}
}
&.compact-scale {
padding-left: 15px;
padding-right: 15px;
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
.toolbar-tools {
.toolbar-button-wrapper {
margin-left: 35px;
}
}
.search-bar input {
min-width: 190px;
}
.toolbar-go-back span {
max-width: 120px;
}
.grid .file-wrapper {
.icon-item {
margin-bottom: 15px;
}
}
}
&.full-scale {
padding-left: 15px;
padding-right: 15px;
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
}
}
</style>
@@ -0,0 +1,92 @@
<template>
<AuthContentWrapper ref="auth">
<!--Verify share link by password-->
<AuthContent name="password" :visible="true">
<img class="logo" :src="config.app_logo" :alt="config.app_name">
<h1>Your Share Link is Protected</h1>
<h2>Please type the password to get shared content:</h2>
<ValidationObserver @submit.prevent="sharedProtected" ref="sharedProtected" v-slot="{ invalid }" tag="form" class="form inline-form">
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="E-Mail" rules="required" v-slot="{ errors }">
<input v-model="password" placeholder="Type password" type="password" :class="{'is-error': errors[0]}"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton icon="chevron-right" text="Submit" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import AuthContentWrapper from '@/components/VueFileManagerComponents/Auth/AuthContentWrapper'
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContent from '@/components/VueFileManagerComponents/Auth/AuthContent'
import AuthButton from '@/components/VueFileManagerComponents/Auth/AuthButton'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import axios from 'axios'
export default {
name: 'SharedContent',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
required,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
checkedAccount: undefined,
isLoading: false,
password: '',
}
},
methods: {
async sharedProtected() {
// Validate fields
const isValid = await this.$refs.sharedProtected.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/share/check', {
password: this.password,
token: this.$route.query.token
})
.then(response => {
// End loading
this.isLoading = false
console.log(response.data);
})
.catch(error => {
// todo: catch error
// End loading
this.isLoading = false
})
},
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth';
</style>
+1 -1
View File
@@ -79,7 +79,7 @@ input[type="email"] {
appearance: none; appearance: none;
font-weight: 700; font-weight: 700;
outline: 0; outline: 0;
min-width: 310px; width: 100%;
&.is-error { &.is-error {
border-color: $danger; border-color: $danger;
+57
View File
@@ -0,0 +1,57 @@
// Forms
.form-wrapper {
padding: 0 20px;
}
.input-wrapper {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
input {
width: 100%;
color: $text;
&.is-error {
border-color: $danger;
box-shadow: 0 0 7px rgba($danger, 0.3);
}
}
}
.inline-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
&.icon-append {
.input-text {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.icon {
background: $theme_light;
padding: 14px 18px;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
@include font-size(16);
text-align: center;
svg path {
fill: white;
}
}
}
}
.input-label {
@include font-size(15);
color: $text;
font-weight: 700;
margin-bottom: 5px;
display: block;
}
+6 -4
View File
@@ -2,15 +2,17 @@
$text: #1b2539; $text: #1b2539;
$text-muted: #667b90; $text-muted: #667b90;
$light_mode_border: rgba(0, 0, 0, 0.02);
$theme: #00BC7E; $theme: #00BC7E;
$danger: #d22323; $theme_light: #4ECDA5;
$light_mode_border: rgba(0, 0, 0, 0.02);
$danger: #fd397a;
$light_text: #A4ADB6; $light_text: #A4ADB6;
$light_background: #f6f6f6; $light_background: #f6f6f6;
$dark_background: #EBEBEB; $dark_background: #EBEBEB;
$shadow: 0 7px 25px 1px rgba(0, 0, 0, 0.12); $shadow: 0 7px 25px 1px rgba(0, 0, 0, 0.12);
$light_mode_popup_shadow: 0 10px 50px rgba(0, 0, 0, .10); $light_mode_popup_shadow: 0 15px 50px 10px rgba(26,38,74,0.12);
$light_mode_vignette: rgba(255, 255, 255, 0.80); $light_mode_vignette: rgba(9, 8, 12, 0.15);
// Dark Mode // Dark Mode
$dark_mode_vignette: rgba(0, 0, 0, 0.3); $dark_mode_vignette: rgba(0, 0, 0, 0.3);
+4
View File
@@ -60,4 +60,8 @@ Route::group(['middleware' => ['auth:api', 'auth.cookie']], function () {
Route::post('/move-item', 'FileManagerController@move_item'); Route::post('/move-item', 'FileManagerController@move_item');
Route::get('/search', 'FileManagerController@search'); Route::get('/search', 'FileManagerController@search');
Route::get('/trash', 'FileManagerController@trash'); Route::get('/trash', 'FileManagerController@trash');
// Sharing routes
Route::post('/share/generate', 'FileSharingController@generate_link');
Route::post('/share/check', 'FileSharingController@check_password');
}); });
+6
View File
@@ -23,4 +23,10 @@ mix.js('resources/js/main.js', 'public/js')
} }
}, },
}) })
/*.options({
hmrOptions: {
host: '192.168.1.131',
port: '8080'
},
})*/
.disableNotifications(); .disableNotifications();