v1.5-alpha.1

This commit is contained in:
carodej
2020-05-15 17:31:25 +02:00
parent cfecf542ca
commit 41656235fc
97 changed files with 4108 additions and 2118 deletions
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\FileBrowser; namespace App\Http\Controllers\FileBrowser;
use App\Http\Requests\FileBrowser\SearchRequest; use App\Http\Requests\FileBrowser\SearchRequest;
use App\User;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -80,6 +81,35 @@ class BrowseController extends Controller
return collect([$folders, $files])->collapse(); return collect([$folders, $files])->collapse();
} }
/**
* Get latest user uploads
*
* @return mixed
*/
public function latest() {
// Get User
$user = User::with(['latest_uploads'])
->where('id', Auth::id())
->first();
return $user->latest_uploads->makeHidden(['user_id', 'basename']);
}
/**
* Get participant uploads
*
* @return mixed
*/
public function participant_uploads() {
// Get User
$uploads = FileManagerFile::where('user_id', Auth::id())
->whereUserScope('editor')->get();
return $uploads;
}
/** /**
* Get directory with files * Get directory with files
* *
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Share\AuthenticateShareRequest; use App\Http\Requests\Share\AuthenticateShareRequest;
use App\Http\Resources\ShareResource; use App\Http\Resources\ShareResource;
use App\Http\Tools\Guardian; use App\Http\Tools\Guardian;
use http\Env\Response;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -32,7 +33,7 @@ class FileSharingController extends Controller
$shared = Share::where(\DB::raw('BINARY `token`'), $token) $shared = Share::where(\DB::raw('BINARY `token`'), $token)
->first(); ->first();
if (!$shared) { if (! $shared) {
return view("index"); return view("index");
} }
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\User; namespace App\Http\Controllers\User;
use App\FileManagerFolder;
use App\Http\Tools\Demo; use App\Http\Tools\Demo;
use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
@@ -26,11 +27,17 @@ class AccountController extends Controller
->where('id', Auth::id()) ->where('id', Auth::id())
->first(); ->first();
// Get folder tree
$tree = FileManagerFolder::with('folders:id,parent_id,unique_id,name')
->where('parent_id', 0)
->where('user_id', $user->id)
->get(['id', 'parent_id', 'unique_id', 'name']);
return [ return [
'user' => $user->only(['name', 'email', 'avatar']), 'user' => $user->only(['name', 'email', 'avatar']),
'favourites' => $user->favourites->makeHidden(['pivot']), 'favourites' => $user->favourites->makeHidden(['pivot']),
'latest_uploads' => $user->latest_uploads->makeHidden(['user_id', 'basename']), 'tree' => $tree,
'storage' => [ 'storage' => [
'used' => Metric::bytes($user->used_capacity)->format(), 'used' => Metric::bytes($user->used_capacity)->format(),
'capacity' => format_gigabytes(config('vuefilemanager.user_storage_capacity')), 'capacity' => format_gigabytes(config('vuefilemanager.user_storage_capacity')),
'percentage' => get_storage_fill_percentage($user->used_capacity, config('vuefilemanager.user_storage_capacity')), 'percentage' => get_storage_fill_percentage($user->used_capacity, config('vuefilemanager.user_storage_capacity')),
@@ -48,9 +55,9 @@ class AccountController extends Controller
{ {
// Validate request // Validate request
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'avatar' => 'file', 'avatar' => 'file',
'name' => 'string', 'name' => 'string',
'value' => 'string', 'value' => 'string',
]); ]);
// Return error // Return error
+1 -1
View File
@@ -145,7 +145,7 @@ class User extends Authenticatable
*/ */
public function latest_uploads() { public function latest_uploads() {
return $this->hasMany(FileManagerFile::class)->orderBy('created_at', 'DESC')->take(7); return $this->hasMany(FileManagerFile::class)->orderBy('created_at', 'DESC')->take(40);
} }
/** /**
+1 -1
View File
@@ -2,7 +2,7 @@
return [ return [
'version' => '1.4.2', 'version' => '1.5',
// Your app name // Your app name
'app_name' => 'VueFileManager', 'app_name' => 'VueFileManager',
+13
View File
@@ -1671,6 +1671,11 @@
} }
} }
}, },
"babel-helper-vue-jsx-merge-props": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
"integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
},
"babel-loader": { "babel-loader": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
@@ -10359,6 +10364,14 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
}, },
"vue-feather-icons": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/vue-feather-icons/-/vue-feather-icons-5.0.0.tgz",
"integrity": "sha512-uvi2l3i0aeRRzG3vX24/AH+8BkcXkSJboAv8XkUZ6aPkEC9n6LXDcKp5/ho+3moVQ9wlga3N6BjL89pmkHBzPw==",
"requires": {
"babel-helper-vue-jsx-merge-props": "^2.0.2"
}
},
"vue-hot-reload-api": { "vue-hot-reload-api": {
"version": "2.3.4", "version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
+1
View File
@@ -26,6 +26,7 @@
"node-sass": "^4.14.0", "node-sass": "^4.14.0",
"vee-validate": "^3.3.0", "vee-validate": "^3.3.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-feather-icons": "^5.0.0",
"vue-i18n": "^8.17.4", "vue-i18n": "^8.17.4",
"vue-router": "^3.1.6", "vue-router": "^3.1.6",
"vuex": "^3.3.0" "vuex": "^3.3.0"
+1 -1
View File
File diff suppressed because one or more lines are too long
+213 -1
View File
@@ -1,4 +1,216 @@
{ {
"/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.9a4ffec7a76c1b950830.hot-update.js": "/js/main.9a4ffec7a76c1b950830.hot-update.js",
"/js/main.c1573e91bed87a15ffef.hot-update.js": "/js/main.c1573e91bed87a15ffef.hot-update.js",
"/js/main.2496d75d386d00064417.hot-update.js": "/js/main.2496d75d386d00064417.hot-update.js",
"/js/main.013ceb75b1bf0e141b26.hot-update.js": "/js/main.013ceb75b1bf0e141b26.hot-update.js",
"/js/main.db3a428e25bff41c3723.hot-update.js": "/js/main.db3a428e25bff41c3723.hot-update.js",
"/js/main.46ddf30da37231310b6b.hot-update.js": "/js/main.46ddf30da37231310b6b.hot-update.js",
"/js/main.6a3838d1437c8c764776.hot-update.js": "/js/main.6a3838d1437c8c764776.hot-update.js",
"/js/main.e0f95966baa9e2d001db.hot-update.js": "/js/main.e0f95966baa9e2d001db.hot-update.js",
"/js/main.7a53126cf46010fc1987.hot-update.js": "/js/main.7a53126cf46010fc1987.hot-update.js",
"/js/main.05df349f8999d3a70dcc.hot-update.js": "/js/main.05df349f8999d3a70dcc.hot-update.js",
"/js/main.b3df17ce1d00b834d523.hot-update.js": "/js/main.b3df17ce1d00b834d523.hot-update.js",
"/js/main.a7bdfebc3c7c81757275.hot-update.js": "/js/main.a7bdfebc3c7c81757275.hot-update.js",
"/js/main.b4703f4b691febc87771.hot-update.js": "/js/main.b4703f4b691febc87771.hot-update.js",
"/js/main.9011812c86e8bda6014e.hot-update.js": "/js/main.9011812c86e8bda6014e.hot-update.js",
"/js/main.3736c833209c79dd36f3.hot-update.js": "/js/main.3736c833209c79dd36f3.hot-update.js",
"/js/main.c486cae59721fcb8d2e2.hot-update.js": "/js/main.c486cae59721fcb8d2e2.hot-update.js",
"/js/main.43677a4bde29458f485d.hot-update.js": "/js/main.43677a4bde29458f485d.hot-update.js",
"/js/main.66fc64b7cc4b7123b5aa.hot-update.js": "/js/main.66fc64b7cc4b7123b5aa.hot-update.js",
"/js/main.41bae96b129129c8f285.hot-update.js": "/js/main.41bae96b129129c8f285.hot-update.js",
"/js/main.db3a48ceef0177374498.hot-update.js": "/js/main.db3a48ceef0177374498.hot-update.js",
"/js/main.8fd98bd943661d02f03a.hot-update.js": "/js/main.8fd98bd943661d02f03a.hot-update.js",
"/js/main.0c8fb4ef996784ecba37.hot-update.js": "/js/main.0c8fb4ef996784ecba37.hot-update.js",
"/js/main.906d9b3d72ff1b1d79a7.hot-update.js": "/js/main.906d9b3d72ff1b1d79a7.hot-update.js",
"/js/main.2fcc8cefed2e5702040d.hot-update.js": "/js/main.2fcc8cefed2e5702040d.hot-update.js",
"/js/main.75ff3612c2490cb2e40d.hot-update.js": "/js/main.75ff3612c2490cb2e40d.hot-update.js",
"/js/main.6a00ba337c54f793bba4.hot-update.js": "/js/main.6a00ba337c54f793bba4.hot-update.js",
"/js/main.da006123f1e03d2bcc92.hot-update.js": "/js/main.da006123f1e03d2bcc92.hot-update.js",
"/js/main.e219de194687e3d70948.hot-update.js": "/js/main.e219de194687e3d70948.hot-update.js",
"/js/main.94b3786c9089a8383469.hot-update.js": "/js/main.94b3786c9089a8383469.hot-update.js",
"/js/main.281b345ecdc7ca542158.hot-update.js": "/js/main.281b345ecdc7ca542158.hot-update.js",
"/js/main.f4510859f5397aa6a991.hot-update.js": "/js/main.f4510859f5397aa6a991.hot-update.js",
"/js/main.fc87ed4e22905cf6e7f0.hot-update.js": "/js/main.fc87ed4e22905cf6e7f0.hot-update.js",
"/js/main.e2225b5b67c9eed50400.hot-update.js": "/js/main.e2225b5b67c9eed50400.hot-update.js",
"/js/main.65e9e758f35bc6182f74.hot-update.js": "/js/main.65e9e758f35bc6182f74.hot-update.js",
"/js/main.ee60d93ee2eeb1869262.hot-update.js": "/js/main.ee60d93ee2eeb1869262.hot-update.js",
"/js/main.62e9d4ca11c61479faed.hot-update.js": "/js/main.62e9d4ca11c61479faed.hot-update.js",
"/js/main.81a823d8b5035716d6f8.hot-update.js": "/js/main.81a823d8b5035716d6f8.hot-update.js",
"/js/main.730819f5aef97dab3c0a.hot-update.js": "/js/main.730819f5aef97dab3c0a.hot-update.js",
"/js/main.b50f2feb59558a4d2e8a.hot-update.js": "/js/main.b50f2feb59558a4d2e8a.hot-update.js",
"/js/main.2e9b8768c31ca95de317.hot-update.js": "/js/main.2e9b8768c31ca95de317.hot-update.js",
"/js/main.8ad2d7f4f8b4ea94c513.hot-update.js": "/js/main.8ad2d7f4f8b4ea94c513.hot-update.js",
"/js/main.05a917f18ce609220d68.hot-update.js": "/js/main.05a917f18ce609220d68.hot-update.js",
"/js/main.e46948c0117a686a1bad.hot-update.js": "/js/main.e46948c0117a686a1bad.hot-update.js",
"/js/main.175b4c582ca1d6d54044.hot-update.js": "/js/main.175b4c582ca1d6d54044.hot-update.js",
"/js/main.aa7b63ada5b60d1a06be.hot-update.js": "/js/main.aa7b63ada5b60d1a06be.hot-update.js",
"/js/main.175acbe8df535a093bc9.hot-update.js": "/js/main.175acbe8df535a093bc9.hot-update.js",
"/js/main.d9c93f3d3c1b48e395c8.hot-update.js": "/js/main.d9c93f3d3c1b48e395c8.hot-update.js",
"/js/main.0bf746d0a2700b6a0825.hot-update.js": "/js/main.0bf746d0a2700b6a0825.hot-update.js",
"/js/main.6aeea20e4837205d9f71.hot-update.js": "/js/main.6aeea20e4837205d9f71.hot-update.js",
"/js/main.75be313981585885c19b.hot-update.js": "/js/main.75be313981585885c19b.hot-update.js",
"/js/main.f107c620f8f074c4b0d6.hot-update.js": "/js/main.f107c620f8f074c4b0d6.hot-update.js",
"/js/main.bc4702a35db6d7f525c1.hot-update.js": "/js/main.bc4702a35db6d7f525c1.hot-update.js",
"/js/main.d047491e33aaf7c4272a.hot-update.js": "/js/main.d047491e33aaf7c4272a.hot-update.js",
"/js/main.27762d9425b3e41a56f6.hot-update.js": "/js/main.27762d9425b3e41a56f6.hot-update.js",
"/js/main.6b32539c74f058c4202c.hot-update.js": "/js/main.6b32539c74f058c4202c.hot-update.js",
"/js/main.f5f68ed520fafa89950c.hot-update.js": "/js/main.f5f68ed520fafa89950c.hot-update.js",
"/js/main.1e27c14375907bf60254.hot-update.js": "/js/main.1e27c14375907bf60254.hot-update.js",
"/js/main.e9d94282271512dee765.hot-update.js": "/js/main.e9d94282271512dee765.hot-update.js",
"/js/main.c26a5d38a19bb110f583.hot-update.js": "/js/main.c26a5d38a19bb110f583.hot-update.js",
"/js/main.3be90dc14a32237bbd12.hot-update.js": "/js/main.3be90dc14a32237bbd12.hot-update.js",
"/js/main.a184c4223e2290d0e5be.hot-update.js": "/js/main.a184c4223e2290d0e5be.hot-update.js",
"/js/main.56d0e49040b82122b25e.hot-update.js": "/js/main.56d0e49040b82122b25e.hot-update.js",
"/js/main.f91b6d35b547aa0f4c68.hot-update.js": "/js/main.f91b6d35b547aa0f4c68.hot-update.js",
"/js/main.f8fa29ca4f13fffdce5e.hot-update.js": "/js/main.f8fa29ca4f13fffdce5e.hot-update.js",
"/js/main.76d04779d7c61cec900e.hot-update.js": "/js/main.76d04779d7c61cec900e.hot-update.js",
"/js/main.c2c2271a1f1eb81bc0f9.hot-update.js": "/js/main.c2c2271a1f1eb81bc0f9.hot-update.js",
"/js/main.e51b16966f9f48d355e8.hot-update.js": "/js/main.e51b16966f9f48d355e8.hot-update.js",
"/js/main.41ae2db21d24f79f1f1a.hot-update.js": "/js/main.41ae2db21d24f79f1f1a.hot-update.js",
"/js/main.ca923845d9496ccf9a16.hot-update.js": "/js/main.ca923845d9496ccf9a16.hot-update.js",
"/js/main.8ed5245a993756bdd5a9.hot-update.js": "/js/main.8ed5245a993756bdd5a9.hot-update.js",
"/js/main.0e60352530acf042bdeb.hot-update.js": "/js/main.0e60352530acf042bdeb.hot-update.js",
"/js/main.2d636142b38d4d55cc32.hot-update.js": "/js/main.2d636142b38d4d55cc32.hot-update.js",
"/js/main.33bd6a17e1a39b3a64fe.hot-update.js": "/js/main.33bd6a17e1a39b3a64fe.hot-update.js",
"/js/main.126d0d04a13f63ffb359.hot-update.js": "/js/main.126d0d04a13f63ffb359.hot-update.js",
"/js/main.a82bb0a24bc2dbba24b5.hot-update.js": "/js/main.a82bb0a24bc2dbba24b5.hot-update.js",
"/js/main.4b4834fd74ddc582671c.hot-update.js": "/js/main.4b4834fd74ddc582671c.hot-update.js",
"/js/main.590c813c8d8ce9325563.hot-update.js": "/js/main.590c813c8d8ce9325563.hot-update.js",
"/js/main.713a4650e3653a8083e8.hot-update.js": "/js/main.713a4650e3653a8083e8.hot-update.js",
"/js/main.ee26dd2fd8ee88f9eb81.hot-update.js": "/js/main.ee26dd2fd8ee88f9eb81.hot-update.js",
"/js/main.09e8cddd553747923c04.hot-update.js": "/js/main.09e8cddd553747923c04.hot-update.js",
"/js/main.b265758be1eb7bacf4ce.hot-update.js": "/js/main.b265758be1eb7bacf4ce.hot-update.js",
"/js/main.811cd46163a1f38b9581.hot-update.js": "/js/main.811cd46163a1f38b9581.hot-update.js",
"/js/main.cdfdb287ad2d56a5d92a.hot-update.js": "/js/main.cdfdb287ad2d56a5d92a.hot-update.js",
"/js/main.771b6994f834bd13c5b9.hot-update.js": "/js/main.771b6994f834bd13c5b9.hot-update.js",
"/js/main.347f3bb1df2245165ad0.hot-update.js": "/js/main.347f3bb1df2245165ad0.hot-update.js",
"/js/main.918351da11b0a9b61ac2.hot-update.js": "/js/main.918351da11b0a9b61ac2.hot-update.js",
"/js/main.f3a7f2f99dec221c4fa3.hot-update.js": "/js/main.f3a7f2f99dec221c4fa3.hot-update.js",
"/js/main.8e0d48026bea459f7fea.hot-update.js": "/js/main.8e0d48026bea459f7fea.hot-update.js",
"/js/main.5938fda9f07c3fe2c8c1.hot-update.js": "/js/main.5938fda9f07c3fe2c8c1.hot-update.js",
"/js/main.1890a16b03ca520f0a02.hot-update.js": "/js/main.1890a16b03ca520f0a02.hot-update.js",
"/js/main.771c1dbc13bfd10787c4.hot-update.js": "/js/main.771c1dbc13bfd10787c4.hot-update.js",
"/js/main.781c07cbda6ffc31dc40.hot-update.js": "/js/main.781c07cbda6ffc31dc40.hot-update.js",
"/js/main.4f97e7df7702cda6bf2d.hot-update.js": "/js/main.4f97e7df7702cda6bf2d.hot-update.js",
"/js/main.465571060eff0d3fa288.hot-update.js": "/js/main.465571060eff0d3fa288.hot-update.js",
"/js/main.1d2e374e3082b5170fce.hot-update.js": "/js/main.1d2e374e3082b5170fce.hot-update.js",
"/js/main.18ebf87ae63353c1e063.hot-update.js": "/js/main.18ebf87ae63353c1e063.hot-update.js",
"/js/main.d32df851cc5fb8c90157.hot-update.js": "/js/main.d32df851cc5fb8c90157.hot-update.js",
"/js/main.e9698a217e749c50f9ab.hot-update.js": "/js/main.e9698a217e749c50f9ab.hot-update.js",
"/js/main.230c4d67179a05e1048c.hot-update.js": "/js/main.230c4d67179a05e1048c.hot-update.js",
"/js/main.a8954b1fd49d0d6bee63.hot-update.js": "/js/main.a8954b1fd49d0d6bee63.hot-update.js",
"/js/main.bd5093999405251d5b34.hot-update.js": "/js/main.bd5093999405251d5b34.hot-update.js",
"/js/main.225a11eddb8d0ec2a92f.hot-update.js": "/js/main.225a11eddb8d0ec2a92f.hot-update.js",
"/js/main.3636188be5e36d684855.hot-update.js": "/js/main.3636188be5e36d684855.hot-update.js",
"/js/main.9cae767dbe2ad8bd70fb.hot-update.js": "/js/main.9cae767dbe2ad8bd70fb.hot-update.js",
"/js/main.5b7aab65cd03a9489f9c.hot-update.js": "/js/main.5b7aab65cd03a9489f9c.hot-update.js",
"/js/main.39510cc3b23189a7de7d.hot-update.js": "/js/main.39510cc3b23189a7de7d.hot-update.js",
"/js/main.8c8334291c620cc95ad8.hot-update.js": "/js/main.8c8334291c620cc95ad8.hot-update.js",
"/js/main.74c6814faa0fd4817bc1.hot-update.js": "/js/main.74c6814faa0fd4817bc1.hot-update.js",
"/js/main.849ad6dc3cdffe9b1b20.hot-update.js": "/js/main.849ad6dc3cdffe9b1b20.hot-update.js",
"/js/main.f395f2fbb2f3b64cc6f1.hot-update.js": "/js/main.f395f2fbb2f3b64cc6f1.hot-update.js",
"/js/main.18d0e8348fd8aa793839.hot-update.js": "/js/main.18d0e8348fd8aa793839.hot-update.js",
"/js/main.fc377ac6d969c7293455.hot-update.js": "/js/main.fc377ac6d969c7293455.hot-update.js",
"/js/main.b22b433586703b7576b4.hot-update.js": "/js/main.b22b433586703b7576b4.hot-update.js",
"/js/main.7b47ddfc9f3a3d983cde.hot-update.js": "/js/main.7b47ddfc9f3a3d983cde.hot-update.js",
"/js/main.a6f75e262f697452f523.hot-update.js": "/js/main.a6f75e262f697452f523.hot-update.js",
"/js/main.e8f47f9fa5312f922155.hot-update.js": "/js/main.e8f47f9fa5312f922155.hot-update.js",
"/js/main.73357d64beeae3e97de3.hot-update.js": "/js/main.73357d64beeae3e97de3.hot-update.js",
"/js/main.ee16315e9e65b0852dd5.hot-update.js": "/js/main.ee16315e9e65b0852dd5.hot-update.js",
"/js/main.88913702eaef335e9e02.hot-update.js": "/js/main.88913702eaef335e9e02.hot-update.js",
"/js/main.db6ac98cf398928c2e73.hot-update.js": "/js/main.db6ac98cf398928c2e73.hot-update.js",
"/js/main.4f12951001656503a158.hot-update.js": "/js/main.4f12951001656503a158.hot-update.js",
"/js/main.2db2982c3308f8cccd4c.hot-update.js": "/js/main.2db2982c3308f8cccd4c.hot-update.js",
"/js/main.a86fdc224969debff737.hot-update.js": "/js/main.a86fdc224969debff737.hot-update.js",
"/js/main.e6fd5ecac90e63e12a02.hot-update.js": "/js/main.e6fd5ecac90e63e12a02.hot-update.js",
"/js/main.d887cfdc43db76b7faa2.hot-update.js": "/js/main.d887cfdc43db76b7faa2.hot-update.js",
"/js/main.f13944ff1e78574af5b5.hot-update.js": "/js/main.f13944ff1e78574af5b5.hot-update.js",
"/js/main.1605a1b0627d94f11dbc.hot-update.js": "/js/main.1605a1b0627d94f11dbc.hot-update.js",
"/js/main.22eb174299088da1c3fa.hot-update.js": "/js/main.22eb174299088da1c3fa.hot-update.js",
"/js/main.a5225ccfa63fc20acf67.hot-update.js": "/js/main.a5225ccfa63fc20acf67.hot-update.js",
"/js/main.9a90c8e6884fcf574b9a.hot-update.js": "/js/main.9a90c8e6884fcf574b9a.hot-update.js",
"/js/main.fd70edb50c7cf7b69785.hot-update.js": "/js/main.fd70edb50c7cf7b69785.hot-update.js",
"/js/main.2eeb2743f199bbf38613.hot-update.js": "/js/main.2eeb2743f199bbf38613.hot-update.js",
"/js/main.b3736284cc46395265b1.hot-update.js": "/js/main.b3736284cc46395265b1.hot-update.js",
"/js/main.66b85201485b4a7421e8.hot-update.js": "/js/main.66b85201485b4a7421e8.hot-update.js",
"/js/main.f8e3e20d4767d7c31781.hot-update.js": "/js/main.f8e3e20d4767d7c31781.hot-update.js",
"/js/main.19e1852773f997b06d2b.hot-update.js": "/js/main.19e1852773f997b06d2b.hot-update.js",
"/js/main.b876869c2a9bbe11f29a.hot-update.js": "/js/main.b876869c2a9bbe11f29a.hot-update.js",
"/js/main.d21c32c24c9eb00a499a.hot-update.js": "/js/main.d21c32c24c9eb00a499a.hot-update.js",
"/js/main.497bde486c82480961bb.hot-update.js": "/js/main.497bde486c82480961bb.hot-update.js",
"/js/main.e36bec81b81e41aca056.hot-update.js": "/js/main.e36bec81b81e41aca056.hot-update.js",
"/js/main.512efb656f2451a94ae4.hot-update.js": "/js/main.512efb656f2451a94ae4.hot-update.js",
"/js/main.f3dc0ec60d2ab5f95622.hot-update.js": "/js/main.f3dc0ec60d2ab5f95622.hot-update.js",
"/js/main.9da7bd56b66dcb221f0d.hot-update.js": "/js/main.9da7bd56b66dcb221f0d.hot-update.js",
"/js/main.7754f0dfc22da448a4e6.hot-update.js": "/js/main.7754f0dfc22da448a4e6.hot-update.js",
"/js/main.4487182e61023c6ea81e.hot-update.js": "/js/main.4487182e61023c6ea81e.hot-update.js",
"/js/main.f004735dd9650ba0cb69.hot-update.js": "/js/main.f004735dd9650ba0cb69.hot-update.js",
"/js/main.1140b2605fe4b2e37b5b.hot-update.js": "/js/main.1140b2605fe4b2e37b5b.hot-update.js",
"/js/main.971b052f4f90d9abebad.hot-update.js": "/js/main.971b052f4f90d9abebad.hot-update.js",
"/js/main.a4ca9ab902b0a36f79da.hot-update.js": "/js/main.a4ca9ab902b0a36f79da.hot-update.js",
"/js/main.677e9d63a56b101c8cc5.hot-update.js": "/js/main.677e9d63a56b101c8cc5.hot-update.js",
"/js/main.0dc9c63f4e16799a8d7d.hot-update.js": "/js/main.0dc9c63f4e16799a8d7d.hot-update.js",
"/js/main.13b1fd1d2a5776adbcc3.hot-update.js": "/js/main.13b1fd1d2a5776adbcc3.hot-update.js",
"/js/main.fcd30219a79ae88d35b9.hot-update.js": "/js/main.fcd30219a79ae88d35b9.hot-update.js",
"/js/main.7948463ef3bb7055317f.hot-update.js": "/js/main.7948463ef3bb7055317f.hot-update.js",
"/js/main.c125e420ab4fe4c9f7d3.hot-update.js": "/js/main.c125e420ab4fe4c9f7d3.hot-update.js",
"/js/main.fbcf324618f293514643.hot-update.js": "/js/main.fbcf324618f293514643.hot-update.js",
"/js/main.5cf641b4d0bf252e9459.hot-update.js": "/js/main.5cf641b4d0bf252e9459.hot-update.js",
"/js/main.5b05d1725e7ea0a4bdc7.hot-update.js": "/js/main.5b05d1725e7ea0a4bdc7.hot-update.js",
"/js/main.77c81b5a940ec02ad546.hot-update.js": "/js/main.77c81b5a940ec02ad546.hot-update.js",
"/js/main.db4f6f2bf820e0c51e3e.hot-update.js": "/js/main.db4f6f2bf820e0c51e3e.hot-update.js",
"/js/main.dc61645ae230a8f1c693.hot-update.js": "/js/main.dc61645ae230a8f1c693.hot-update.js",
"/js/main.7725a1c58241a6c96525.hot-update.js": "/js/main.7725a1c58241a6c96525.hot-update.js",
"/js/main.40a5066ec9316e75e78e.hot-update.js": "/js/main.40a5066ec9316e75e78e.hot-update.js",
"/js/main.97f1c64eab8ba538f510.hot-update.js": "/js/main.97f1c64eab8ba538f510.hot-update.js",
"/js/main.6bf46e7d071777d819ac.hot-update.js": "/js/main.6bf46e7d071777d819ac.hot-update.js",
"/js/main.d5bce653b584abdec0e5.hot-update.js": "/js/main.d5bce653b584abdec0e5.hot-update.js",
"/js/main.a158e667c7e797193ce1.hot-update.js": "/js/main.a158e667c7e797193ce1.hot-update.js",
"/js/main.3f419ca1efc24822133c.hot-update.js": "/js/main.3f419ca1efc24822133c.hot-update.js",
"/js/main.cd2493fb57be52830881.hot-update.js": "/js/main.cd2493fb57be52830881.hot-update.js",
"/js/main.06d9fe7643a92be279ab.hot-update.js": "/js/main.06d9fe7643a92be279ab.hot-update.js",
"/js/main.ee38321d3a966fca7d05.hot-update.js": "/js/main.ee38321d3a966fca7d05.hot-update.js",
"/js/main.42a81ed8c7e29b5813f1.hot-update.js": "/js/main.42a81ed8c7e29b5813f1.hot-update.js",
"/js/main.3360b0fb7831cebd90a5.hot-update.js": "/js/main.3360b0fb7831cebd90a5.hot-update.js",
"/js/main.65d9140c15f311c37f83.hot-update.js": "/js/main.65d9140c15f311c37f83.hot-update.js",
"/js/main.58cfd53649eace0c70b1.hot-update.js": "/js/main.58cfd53649eace0c70b1.hot-update.js",
"/js/main.cb6270bbc347b69aca43.hot-update.js": "/js/main.cb6270bbc347b69aca43.hot-update.js",
"/js/main.4697cc6965085628186a.hot-update.js": "/js/main.4697cc6965085628186a.hot-update.js",
"/js/main.6dc892cffa3e5926b3c8.hot-update.js": "/js/main.6dc892cffa3e5926b3c8.hot-update.js",
"/js/main.f6b489ab9bfe6884487d.hot-update.js": "/js/main.f6b489ab9bfe6884487d.hot-update.js",
"/js/main.174349a38afa813b263b.hot-update.js": "/js/main.174349a38afa813b263b.hot-update.js",
"/js/main.8a6d60407d80835ecbad.hot-update.js": "/js/main.8a6d60407d80835ecbad.hot-update.js",
"/js/main.2746deb0d433bbd85f68.hot-update.js": "/js/main.2746deb0d433bbd85f68.hot-update.js",
"/js/main.f85bddc056d7d7b16f99.hot-update.js": "/js/main.f85bddc056d7d7b16f99.hot-update.js",
"/js/main.e10202aa85190653ad73.hot-update.js": "/js/main.e10202aa85190653ad73.hot-update.js",
"/js/main.15de957e562857d144a5.hot-update.js": "/js/main.15de957e562857d144a5.hot-update.js",
"/js/main.75da45714f63bd5486bd.hot-update.js": "/js/main.75da45714f63bd5486bd.hot-update.js",
"/js/main.2070d47c01eb224307b4.hot-update.js": "/js/main.2070d47c01eb224307b4.hot-update.js",
"/js/main.495ebfa70d0a3166d97b.hot-update.js": "/js/main.495ebfa70d0a3166d97b.hot-update.js",
"/js/main.36ff64ad1058f1a77239.hot-update.js": "/js/main.36ff64ad1058f1a77239.hot-update.js",
"/js/main.b9ac2ee5ec64a0a63d2d.hot-update.js": "/js/main.b9ac2ee5ec64a0a63d2d.hot-update.js",
"/js/main.af2a7936bd8b82ef9d98.hot-update.js": "/js/main.af2a7936bd8b82ef9d98.hot-update.js",
"/js/main.267cc5cffe3e293b9d79.hot-update.js": "/js/main.267cc5cffe3e293b9d79.hot-update.js",
"/js/main.ea833f8d49e28fcb63f9.hot-update.js": "/js/main.ea833f8d49e28fcb63f9.hot-update.js",
"/js/main.0e6735e3a459e47c4f3f.hot-update.js": "/js/main.0e6735e3a459e47c4f3f.hot-update.js",
"/js/main.39ffc75da70e1ea506b4.hot-update.js": "/js/main.39ffc75da70e1ea506b4.hot-update.js",
"/js/main.c68d38bdcf92538c0303.hot-update.js": "/js/main.c68d38bdcf92538c0303.hot-update.js",
"/js/main.a0cdb18827fddb4cd559.hot-update.js": "/js/main.a0cdb18827fddb4cd559.hot-update.js",
"/js/main.03aa488609c2be5c87af.hot-update.js": "/js/main.03aa488609c2be5c87af.hot-update.js",
"/js/main.09afd8a92a7772f1062e.hot-update.js": "/js/main.09afd8a92a7772f1062e.hot-update.js",
"/js/main.f0804ab252dad4e91396.hot-update.js": "/js/main.f0804ab252dad4e91396.hot-update.js",
"/js/main.b4cbf800c67983771132.hot-update.js": "/js/main.b4cbf800c67983771132.hot-update.js",
"/js/main.b86cc490a20b731241fd.hot-update.js": "/js/main.b86cc490a20b731241fd.hot-update.js",
"/js/main.bb077fd5365dc113e5ce.hot-update.js": "/js/main.bb077fd5365dc113e5ce.hot-update.js",
"/js/main.3036afc5655ec8f5b6d5.hot-update.js": "/js/main.3036afc5655ec8f5b6d5.hot-update.js",
"/js/main.5ae29336999df63d0ed8.hot-update.js": "/js/main.5ae29336999df63d0ed8.hot-update.js",
"/js/main.30716b58191d830fb6a7.hot-update.js": "/js/main.30716b58191d830fb6a7.hot-update.js",
"/js/main.96730b11d2c8ae2f9757.hot-update.js": "/js/main.96730b11d2c8ae2f9757.hot-update.js",
"/js/main.a1edb6896e4b978b22be.hot-update.js": "/js/main.a1edb6896e4b978b22be.hot-update.js",
"/js/main.d8e3f00bec4b902263ac.hot-update.js": "/js/main.d8e3f00bec4b902263ac.hot-update.js",
"/js/main.917bd2f050edc6b443d9.hot-update.js": "/js/main.917bd2f050edc6b443d9.hot-update.js",
"/js/main.e38019d32e49fdfddadc.hot-update.js": "/js/main.e38019d32e49fdfddadc.hot-update.js",
"/js/main.d7423441df4bdf0cd124.hot-update.js": "/js/main.d7423441df4bdf0cd124.hot-update.js"
} }
+64 -27
View File
@@ -1,57 +1,61 @@
<template> <template>
<div id="vue-file-manager" :class="appSize"> <div id="vue-file-manager" :class="appSize" v-cloak>
<!--System alerts--> <!--System alerts-->
<Alert /> <Alert/>
<div id="application-wrapper" v-if="layout === 'authorized'"> <div id="application-wrapper" v-if="layout === 'authorized'">
<MobileNavigation />
<!--Share Item setup--> <!--Share Item setup-->
<ShareCreate /> <ShareCreate/>
<ShareEdit /> <ShareEdit/>
<!--Move item setup--> <!--Move item setup-->
<MoveItem /> <MoveItem/>
<!--Mobile Menu--> <!--Mobile Menu-->
<MobileMenu /> <MobileMenu/>
<!--Navigation Sidebar--> <!--Navigation Sidebar-->
<Sidebar/> <MenuBar/>
<!--File page--> <!--File page-->
<router-view/> <router-view :class="{'is-scaled-down': isScaledDown}"/>
</div> </div>
<router-view v-if="layout === 'unauthorized'"/> <router-view v-if="layout === 'unauthorized'"/>
<!--Background vignette--> <!--Background vignette-->
<Vignette /> <Vignette/>
</div> </div>
</template> </template>
<script> <script>
import MobileNavigation from '@/components/Others/MobileNavigation'
import MobileMenu from '@/components/FilesView/MobileMenu' import MobileMenu from '@/components/FilesView/MobileMenu'
import ShareCreate from '@/components/Others/ShareCreate' import ShareCreate from '@/components/Others/ShareCreate'
import ShareEdit from '@/components/Others/ShareEdit' import ShareEdit from '@/components/Others/ShareEdit'
import MoveItem from '@/components/Others/MoveItem' import MoveItem from '@/components/Others/MoveItem'
import Vignette from '@/components/Others/Vignette' import Vignette from '@/components/Others/Vignette'
import Sidebar from '@/components/Sidebar/Sidebar' import MenuBar from '@/components/Sidebar/MenuBar'
import Alert from '@/components/FilesView/Alert' import Alert from '@/components/FilesView/Alert'
import {ResizeSensor} from 'css-element-queries' import {ResizeSensor} from 'css-element-queries'
import { includes } from 'lodash' import {includes} from 'lodash'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from "./bus" import {events} from "./bus"
export default { export default {
name: 'app', name: 'app',
components: { components: {
MobileNavigation,
ShareCreate, ShareCreate,
MobileMenu, MobileMenu,
ShareEdit, ShareEdit,
MoveItem, MoveItem,
Vignette, Vignette,
Sidebar, MenuBar,
Alert, Alert,
}, },
computed: { computed: {
@@ -66,6 +70,11 @@
return 'authorized' return 'authorized'
} }
}, },
data() {
return {
isScaledDown: false,
}
},
methods: { methods: {
handleAppResize() { handleAppResize() {
let appView = document.getElementById('vue-file-manager') let appView = document.getElementById('vue-file-manager')
@@ -80,20 +89,41 @@
}, },
}, },
beforeMount() { beforeMount() {
// Store config to vuex // Store config to vuex
this.$store.commit('SET_AUTHORIZED', this.$root.$data.config.hasAuthCookie) this.$store.commit('INIT', {
this.$store.commit('SET_CONFIG', this.$root.$data.config) authCookie: this.$root.$data.config.hasAuthCookie,
config: this.$root.$data.config,
rootDirectory: {
name: this.$t('locations.home'),
location: 'base',
unique_id: 0,
}
})
}, },
mounted() { mounted() {
// 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);
// Handle mobile navigation scale animation
events.$on('show:mobile-navigation', () => this.isScaledDown = true)
events.$on('hide:mobile-navigation', () => this.isScaledDown = false)
events.$on('mobileMenu:show', () => this.isScaledDown = true)
events.$on('fileItem:deselect', () => this.isScaledDown = false)
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@import "@assets/app.scss"; @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;900&display=swap');
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
[v-cloak],
[v-cloak] > * {
display: none
}
* { * {
outline: 0; outline: 0;
@@ -107,6 +137,25 @@
font-size: 16px; font-size: 16px;
} }
#auth {
width: 100%;
height: 100%;
}
#vue-file-manager {
position: absolute;
width: 100%;
height: 100%;
overflow-y: auto;
}
@media only screen and (max-width: 690px) {
.is-scaled-down {
@include transform(scale(0.95));
}
}
// Dark mode support // Dark mode support
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -119,16 +168,4 @@
} }
} }
} }
#auth {
width: 100%;
height: 100%;
}
#vue-file-manager {
position: absolute;
width: 100%;
height: 100%;
overflow-y: auto;
}
</style> </style>
+2 -1
View File
@@ -28,7 +28,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button { .button {
cursor: pointer; cursor: pointer;
@@ -19,8 +19,3 @@
} }
} }
</script> </script>
<style scoped lang="scss">
</style>
@@ -11,7 +11,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss";
#auth { #auth {
height: 100%; height: 100%;
+2 -1
View File
@@ -88,7 +88,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup { .popup {
position: absolute; position: absolute;
@@ -15,10 +15,11 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-base { .button-base {
@include font-size(16); @include font-size(15);
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
transition: 0.15s all ease; transition: 0.15s all ease;
@@ -31,10 +31,11 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-base { .button-base {
@include font-size(16); @include font-size(15);
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
transition: 0.15s all ease; transition: 0.15s all ease;
+334 -101
View File
@@ -7,105 +7,270 @@
ref="contextmenu" ref="contextmenu"
> >
<!--ContextMenu for trash location--> <!--ContextMenu for trash location-->
<ul v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options" ref="list"> <div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" id="menu-list" class="menu-options">
<li class="menu-option" @click="deleteItem" v-if="item"> <ul class="menu-option-group">
{{ $t('context_menu.delete') }} <li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item">
</li> <div class="icon">
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item"> <life-buoy-icon size="17"></life-buoy-icon>
{{ $t('context_menu.restore') }} </div>
</li> <div class="text-label">
<li class="menu-option" @click="$store.dispatch('emptyTrash')"> {{ $t('context_menu.restore') }}
{{ $t('context_menu.empty_trash') }} </div>
</li> </li>
<li class="menu-option" @click="ItemDetail" v-if="item"> <li class="menu-option" @click="$store.dispatch('emptyTrash')">
{{ $t('context_menu.detail') }} <div class="icon">
</li> <trash-icon size="17"></trash-icon>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item"> </div>
{{ $t('context_menu.download') }} <div class="text-label">
</li> {{ $t('context_menu.empty_trash') }}
</ul> </div>
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with MASTER permission--> <!--ContextMenu for Base location with MASTER permission-->
<ul v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options" ref="list"> <div v-if="$isThisLocation(['shared']) && $checkPermission('master')" id="menu-list" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="item && isFolder"> <ul class="menu-option-group">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }} <li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
</li> <div class="icon">
<li class="menu-option" @click="deleteItem" v-if="item"> <star-icon size="17"></star-icon>
{{ $t('context_menu.delete') }} </div>
</li> <div class="text-label">
<li class="menu-option" @click="shareItem" v-if="item"> {{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }} </div>
</li> </li>
<li class="menu-option" @click="ItemDetail" v-if="item"> </ul>
{{ $t('context_menu.detail') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="deleteItem" v-if="item">
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item"> <div class="icon">
{{ $t('context_menu.download') }} <trash-2-icon size="17"></trash-2-icon>
</li> </div>
</ul> <div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="item">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with MASTER permission--> <!--ContextMenu for Base location with MASTER permission-->
<ul v-if="$isThisLocation(['base']) && $checkPermission('master')" class="menu-options" ref="list"> <div v-if="$isThisLocation(['base', 'participant_uploads', 'latest']) && $checkPermission('master')" id="menu-list" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }} <ul class="menu-option-group" v-if="! $isThisLocation(['participant_uploads', 'latest'])">
</li> <li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
<li class="menu-option" @click="createFolder"> <div class="icon">
{{ $t('context_menu.create_folder') }} <star-icon size="17"></star-icon>
</li> </div>
<li class="menu-option" @click="deleteItem" v-if="item"> <div class="text-label">
{{ $t('context_menu.delete') }} {{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li> </div>
<li class="menu-option" @click="moveItem" v-if="item"> </li>
{{ $t('context_menu.move') }} <li class="menu-option" @click="createFolder">
</li> <div class="icon">
<li class="menu-option" @click="shareItem" v-if="item"> <folder-plus-icon size="17"></folder-plus-icon>
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }} </div>
</li> <div class="text-label">
<li class="menu-option" @click="ItemDetail" v-if="item"> {{ $t('context_menu.create_folder') }}
{{ $t('context_menu.detail') }} </div>
</li> </li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item"> </ul>
{{ $t('context_menu.download') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="moveItem" v-if="item">
</ul> <div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="item">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with EDITOR permission--> <!--ContextMenu for Base location with EDITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options" ref="list"> <div v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" id="menu-list" class="menu-options">
<li class="menu-option" @click="createFolder"> <ul class="menu-option-group">
{{ $t('context_menu.create_folder') }} <li class="menu-option" @click="createFolder">
</li> <div class="icon">
<li class="menu-option" @click="deleteItem" v-if="item"> <folder-plus-icon size="17"></folder-plus-icon>
{{ $t('context_menu.delete') }} </div>
</li> <div class="text-label">
<li class="menu-option" @click="moveItem" v-if="item"> {{ $t('context_menu.create_folder') }}
{{ $t('context_menu.move') }} </div>
</li> </li>
<li class="menu-option" @click="ItemDetail" v-if="item"> </ul>
{{ $t('context_menu.detail') }} <ul class="menu-option-group" v-if="item && isFolder">
</li> <li class="menu-option" @click="moveItem" v-if="item">
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item"> <div class="icon">
{{ $t('context_menu.download') }} <corner-down-right-icon size="17"></corner-down-right-icon>
</li> </div>
</ul> <div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group" v-if="item && isFolder">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with VISITOR permission--> <!--ContextMenu for Base location with VISITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options" ref="list"> <div v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" id="menu-list" class="menu-options">
<li class="menu-option" @click="ItemDetail" v-if="item"> <li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }} <div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li> </li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item"> <li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }} <div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li> </li>
</ul> </div>
</div> </div>
</template> </template>
<script> <script>
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
LifeBuoyIcon,
Trash2Icon,
TrashIcon,
StarIcon,
LinkIcon,
EyeIcon,
} from 'vue-feather-icons'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus' import {events} from '@/bus'
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
components: {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
LifeBuoyIcon,
Trash2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: { computed: {
...mapGetters(['app']), ...mapGetters(['app']),
isFolder() { isFolder() {
@@ -145,7 +310,7 @@
}, },
addToFavourites() { addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites // 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 {
this.$store.dispatch('removeFromFavourites', this.item) this.$store.dispatch('removeFromFavourites', this.item)
@@ -159,6 +324,7 @@
) )
}, },
ItemDetail() { ItemDetail() {
// Dispatch load file info detail // Dispatch load file info detail
this.$store.commit('GET_FILEINFO_DETAIL', this.item) this.$store.commit('GET_FILEINFO_DETAIL', this.item)
@@ -180,8 +346,20 @@
// Reset item container // Reset item container
this.item = undefined this.item = undefined
}, },
showContextMenu(event, item) { showFolderActionsMenu() {
let VerticalOffsetArea = item && this.$refs.list.children ? this.$refs.list.children.length * 50 : 50 let container = document.getElementById('folder-actions')
this.positionX = container.offsetLeft + 16
this.positionY = container.offsetTop + 30
// Show context menu
this.isVisible = true
},
showContextMenu(event) {
let parent = document.getElementById('menu-list')
let nodesSameClass = parent.getElementsByClassName("menu-option")
let VerticalOffsetArea = nodesSameClass.length * 50
let HorizontalOffsetArea = 190 let HorizontalOffsetArea = 190
let container = document.getElementById('files-view') let container = document.getElementById('files-view')
@@ -220,15 +398,42 @@
}) })
events.$on('contextMenu:hide', () => (this.closeAndResetContextMenu())) events.$on('contextMenu:hide', () => (this.closeAndResetContextMenu()))
events.$on('folder:actions', folder => {
// Store item
this.item = folder
if (this.isVisible)
this.isVisible = false
else
this.showFolderActionsMenu()
})
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.menu-option {
display: flex;
align-items: center;
.icon {
margin-right: 20px;
line-height: 0;
}
.text-label {
@include font-size(16);
}
}
.contextmenu { .contextmenu {
min-width: 190px; min-width: 250px;
position: absolute; position: absolute;
z-index: 99; z-index: 99;
box-shadow: $shadow; box-shadow: $shadow;
@@ -239,26 +444,47 @@
&.showed { &.showed {
display: block; display: block;
} }
}
.menu-options { .menu-options {
list-style: none; list-style: none;
width: 100%;
margin: 0;
padding: 0;
.menu-option-group {
padding: 5px 0;
border-bottom: 1px solid $light_mode_border;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
.menu-option {
white-space: nowrap;
font-weight: 700;
@include font-size(14);
padding: 15px 20px;
cursor: pointer;
width: 100%; width: 100%;
margin: 0; color: $text;
padding: 0;
.menu-option { &:hover {
white-space: nowrap; background: $light_background;
font-weight: 700;
@include font-size(15);
padding: 15px 30px;
cursor: pointer;
width: 100%;
color: $text;
&:hover { .text-label {
background: $light_background;
color: $theme; color: $theme;
} }
path, line, polyline, rect, circle, polygon {
stroke: $theme;
}
} }
} }
} }
@@ -268,11 +494,18 @@
.contextmenu { .contextmenu {
background: $dark_mode_foreground; background: $dark_mode_foreground;
.menu-options .menu-option { .menu-options {
color: $dark_mode_text_primary;
&:hover { .menu-option-group {
background: $dark_mode_background; border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary;
&:hover {
background: rgba($theme, 0.1);
}
} }
} }
} }
@@ -1,41 +1,57 @@
<template> <template>
<div id="desktop-toolbar" v-if="! $isMinimalScale()"> <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">
<FontAwesomeIcon <chevron-left-icon size="17" :class="{'is-active': browseHistory.length > 1}" class="icon-back"></chevron-left-icon>
v-if="browseHistory.length > 0"
class="icon-back"
icon="chevron-left"
></FontAwesomeIcon>
<span class="back-directory-title"> <span class="back-directory-title">
{{ directoryName }} {{ directoryName }}
</span> </span>
<span @click.stop="folderActions" v-if="browseHistory.length > 1 && $isThisLocation(['base', 'public'])" class="folder-options" id="folder-actions">
<more-horizontal-icon size="14" class="icon-more"></more-horizontal-icon>
</span>
</div> </div>
</div> </div>
<!-- Tools--> <!-- Tools-->
<div class="toolbar-tools"> <div class="toolbar-tools">
<!--Search bar--> <!--Search bar-->
<div class="toolbar-button-wrapper"> <div class="toolbar-button-wrapper">
<SearchBar/> <SearchBar/>
</div> </div>
<!--Files controlls--> <!--Files controlls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor'])"> <div class="toolbar-button-wrapper"
v-if="$checkPermission(['master', 'editor'])">
<ToolbarButtonUpload <ToolbarButtonUpload
source="upload" :class="{'is-inactive': canUploadInView}"
:action="$t('actions.upload')" :action="$t('actions.upload')"
/> />
<ToolbarButton <ToolbarButton
source="trash-alt" source="move"
:action="$t('actions.delete')" :class="{'is-inactive': canMoveInView}"
@click.native="deleteItems" action="Move"
@click.native="moveItem"
/> />
<ToolbarButton <ToolbarButton
v-if="! $isThisLocation(['public'])"
source="share"
:class="{'is-inactive': canShareInView}"
action="Share"
@click.native="shareItem"
/>
<ToolbarButton
source="trash"
:class="{'is-inactive': canDeleteInView}"
:action="$t('actions.delete')"
@click.native="deleteItem"
/>
<ToolbarButton
:class="{'is-inactive': canCreateFolderInView}"
@click.native="createFolder" @click.native="createFolder"
source="folder-plus" source="folder-plus"
:action="$t('actions.create_folder')" :action="$t('actions.create_folder')"
@@ -63,89 +79,108 @@
<script> <script>
import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload' import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload'
import { ChevronLeftIcon, MoreHorizontalIcon } from 'vue-feather-icons'
import UploadProgress from '@/components/FilesView/UploadProgress' import UploadProgress from '@/components/FilesView/UploadProgress'
import ToolbarButton from '@/components/FilesView/ToolbarButton' import ToolbarButton from '@/components/FilesView/ToolbarButton'
import SearchBar from '@/components/FilesView/SearchBar' import SearchBar from '@/components/FilesView/SearchBar'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus' import {events} from '@/bus'
import {last} from 'lodash'
export default { export default {
name: 'ToolBar', name: 'ToolBar',
components: { components: {
ToolbarButtonUpload, ToolbarButtonUpload,
MoreHorizontalIcon,
ChevronLeftIcon,
UploadProgress, UploadProgress,
ToolbarButton, ToolbarButton,
SearchBar SearchBar
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
'FilePreviewType',
'fileInfoVisible', 'fileInfoVisible',
'fileInfoDetail', 'fileInfoDetail',
'currentFolder', 'currentFolder',
'browseHistory', 'browseHistory',
'homeDirectory', 'homeDirectory',
'FilePreviewType',
]), ]),
directoryName() { directoryName() {
return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name
}, },
previousFolder() {
const length = this.browseHistory.length - 2
return this.browseHistory[length] ? this.browseHistory[length] : this.homeDirectory
},
preview() { preview() {
return this.FilePreviewType === 'list' ? 'th' : 'th-list' return this.FilePreviewType === 'list' ? 'th' : 'th-list'
}, },
}, canCreateFolderInView() {
data() { return ! this.$isThisLocation(['base', 'public'])
return { },
isSidebarMenu: false, canDeleteInView() {
return ! this.$isThisLocation(['trash', 'trash-root', 'base', 'participant_uploads', 'latest', 'shared', 'public'])
},
canUploadInView() {
return ! this.$isThisLocation(['base', 'public'])
},
canMoveInView() {
return ! this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared', 'public'])
},
canShareInView() {
return ! this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared', 'public'])
} }
}, },
methods: { methods: {
showSidebarMenu() {
this.isSidebarMenu = ! this.isSidebarMenu
events.$emit('show:sidebar')
},
goBack() { goBack() {
// Get previous folder
let previousFolder = last(this.browseHistory)
if (this.previousFolder.location === 'trash-root') { if (! previousFolder)
return
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash') this.$store.dispatch('getTrash')
this.$store.commit('FLUSH_BROWSER_HISTORY')
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
} else { } else {
if ( this.$isThisLocation('public') ) { if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.previousFolder, true]) this.$store.dispatch('browseShared', [{folder: previousFolder, back: true, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.previousFolder, true]) this.$store.dispatch('getFolder', [{folder: previousFolder, back: true, init: false}])
} }
} }
}, },
deleteItems() { folderActions() {
events.$emit('folder:actions', this.currentFolder)
},
deleteItem() {
events.$emit('items:delete') events.$emit('items:delete')
}, },
createFolder() { createFolder() {
if (! this.$isThisLocation(['trash', 'trash-root'])) this.$createFolder()
this.$createFolder() },
moveItem() {
events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
},
shareItem() {
if (this.fileInfoDetail.shared) {
events.$emit('popup:open', {name: 'share-edit', item: this.fileInfoDetail})
} else {
events.$emit('popup:open', {name: 'share-create', item: this.fileInfoDetail})
}
} }
}, },
created() {
// Listen for hide sidebar
events.$on('show:content', () => {
if (this.isSidebarMenu) this.isSidebarMenu = false
})
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.toolbar-wrapper { .toolbar-wrapper {
padding-top: 15px; padding-top: 10px;
padding-bottom: 15px; padding-bottom: 10px;
display: flex; display: flex;
position: relative; position: relative;
z-index: 2; z-index: 2;
@@ -171,13 +206,48 @@
.icon-back { .icon-back {
vertical-align: middle; vertical-align: middle;
cursor: pointer; cursor: pointer;
margin-right: 12px; margin-right: 6px;
opacity: 0.15;
pointer-events: none;
@include transition(150ms);
&.is-active {
opacity: 1;
pointer-events: initial;
}
} }
.toolbar-go-back { .toolbar-go-back {
cursor: pointer; cursor: pointer;
.folder-options {
vertical-align: middle;
margin-left: 6px;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
svg circle {
stroke: $theme;
}
}
.icon-more {
vertical-align: middle;
}
}
.back-directory-title { .back-directory-title {
@include font-size(15);
line-height: 1; line-height: 1;
font-weight: 700; font-weight: 700;
overflow: hidden; overflow: hidden;
@@ -201,7 +271,7 @@
text-align: right; text-align: right;
.toolbar-button-wrapper { .toolbar-button-wrapper {
margin-left: 75px; margin-left: 28px;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@@ -210,8 +280,13 @@
} }
} }
button { .button {
margin-left: 20px; margin-left: 5px;
&.is-inactive {
opacity: 0.25;
pointer-events: none;
}
&:first-child { &:first-child {
margin-left: 0; margin-left: 0;
@@ -219,6 +294,33 @@
} }
} }
@media only screen and (max-width: 1024px) {
.toolbar-go-back .back-directory-title {
max-width: 120px;
}
.toolbar-tools {
.button {
margin-left: 0;
height: 40px;
width: 40px;
}
.toolbar-button-wrapper {
margin-left: 25px;
}
}
}
@media only screen and (max-width: 960px) {
#desktop-toolbar {
display: none;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.toolbar .directory-name { .toolbar .directory-name {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
@@ -229,6 +331,13 @@
.back-directory-title { .back-directory-title {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
} }
.folder-options {
&:hover {
background: $dark_mode_foreground;
}
}
} }
} }
</style> </style>
@@ -1,21 +1,27 @@
<template> <template>
<div class="empty-message"> <div class="empty-message">
<div class="message"> <div class="message">
<FontAwesomeIcon class="icon" :icon="icon"/> <eye-off-icon v-if="icon === 'eye-off'" size="36" class="icon"></eye-off-icon>
<p>{{ message }}</p> <p>{{ message }}</p>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { EyeOffIcon } from 'vue-feather-icons'
export default { export default {
name: 'EmptyMessage', name: 'EmptyMessage',
props: ['icon', 'message'] props: ['icon', 'message'],
components: {
EyeOffIcon
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.empty-message { .empty-message {
text-align: center; text-align: center;
@@ -35,11 +41,8 @@
} }
.icon { .icon {
@include font-size(36); path, line, polyline, rect, circle {
color: $text; stroke: $text;
path {
fill: $text;
} }
} }
} }
@@ -47,8 +50,9 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.empty-message .message .icon { .empty-message .message .icon {
path {
fill: $dark_mode_text_secondary; path, line, polyline, rect, circle {
stroke: $dark_mode_text_secondary;
} }
} }
} }
@@ -18,7 +18,6 @@
<p v-if="$checkPermission(['master', 'editor'])" class="description">{{ $t('empty_page.description') }}</p> <p v-if="$checkPermission(['master', 'editor'])" class="description">{{ $t('empty_page.description') }}</p>
<ButtonUpload <ButtonUpload
v-if="$checkPermission(['master', 'editor'])" v-if="$checkPermission(['master', 'editor'])"
@input.native="$uploadFiles"
button-style="theme" button-style="theme"
> >
{{ $t('empty_page.call_to_action') }} {{ $t('empty_page.call_to_action') }}
@@ -55,7 +54,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.empty-page { .empty-page {
position: absolute; position: absolute;
@@ -15,7 +15,7 @@
<MobileToolbar /> <MobileToolbar />
<!--Searchbar--> <!--Searchbar-->
<SearchBar v-if="$isMinimalScale()" class="mobile-search"/> <SearchBar class="mobile-search" />
<!--Mobile Actions--> <!--Mobile Actions-->
<MobileActions /> <MobileActions />
@@ -77,7 +77,7 @@
<FileInfoPanel v-if="fileInfoDetail"/> <FileInfoPanel v-if="fileInfoDetail"/>
<!--If file info panel empty show message--> <!--If file info panel empty show message-->
<EmptyMessage v-if="!fileInfoDetail" :message="$t('messages.nothing_to_preview')" icon="eye-slash"/> <EmptyMessage v-if="!fileInfoDetail" :message="$t('messages.nothing_to_preview')" icon="eye-off"/>
</div> </div>
</div> </div>
</template> </template>
@@ -209,7 +209,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-upload { .button-upload {
display: block; display: block;
@@ -218,6 +219,7 @@
} }
.mobile-search { .mobile-search {
display: none;
margin-bottom: 10px; margin-bottom: 10px;
margin-top: 10px; margin-top: 10px;
} }
@@ -276,11 +278,70 @@
transform: translateX(-20px); transform: translateX(-20px);
} }
.file-leave-active { @media only screen and (min-width: 960px) {
position: absolute;
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
} }
@media only screen and (max-width: 660px) { @media only screen and (max-width: 960px) {
.file-info-container {
display: none;
}
.mobile-search {
display: block;
}
}
@media only screen and (max-width: 690px) {
.file-list {
&.grid {
grid-template-columns: repeat(auto-fill, 120px);
}
}
.files-container {
padding-left: 15px;
padding-right: 15px;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
overflow-y: auto;
}
.file-content {
position: absolute;
top: 0;
left: 0px;
right: 0px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
.mobile-search {
margin-bottom: 0;
}
.file-info-container { .file-info-container {
display: none; display: none;
} }
@@ -7,12 +7,15 @@
<div class="flex"> <div class="flex">
<div class="icon"> <div class="icon">
<div class="icon-preview"> <div class="icon-preview">
<FontAwesomeIcon :icon="filePreviewIcon"></FontAwesomeIcon> <image-icon v-if="fileType === 'image'" size="21"></image-icon>
<video-icon v-if="fileType === 'video'" size="21"></video-icon>
<folder-icon v-if="fileType === 'folder'" size="21"></folder-icon>
<file-icon v-if="fileType === 'file'" size="21"></file-icon>
</div> </div>
</div> </div>
<div class="file-info"> <div class="file-info">
<span ref="name" class="name">{{ fileInfoDetail.name }}</span> <span ref="name" class="name">{{ fileInfoDetail.name }}</span>
<span class="mimetype" v-if="fileInfoDetail.mimetype">{{ fileInfoDetail.mimetype }}</span> <span class="mimetype" v-if="fileInfoDetail.mimetype">.{{ fileInfoDetail.mimetype }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -42,8 +45,8 @@
<li v-if="$checkPermission(['master'])" class="list-info-item"> <li v-if="$checkPermission(['master'])" class="list-info-item">
<b>{{ $t('file_detail.where') }}</b> <b>{{ $t('file_detail.where') }}</b>
<div class="action-button" @click="moveItem"> <div class="action-button" @click="moveItem">
<FontAwesomeIcon class="icon" icon="pencil-alt" />
<span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span> <span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div> </div>
</li> </li>
@@ -51,11 +54,13 @@
<li v-if="$checkPermission('master') && fileInfoDetail.shared" class="list-info-item"> <li v-if="$checkPermission('master') && fileInfoDetail.shared" class="list-info-item">
<b>{{ $t('file_detail.shared') }}</b> <b>{{ $t('file_detail.shared') }}</b>
<div class="action-button" @click="shareItemOptions"> <div class="action-button" @click="shareItemOptions">
<FontAwesomeIcon class="icon" :icon="sharedIcon" />
<span>{{ sharedInfo }}</span> <span>{{ sharedInfo }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div> </div>
<div class="sharelink"> <div class="sharelink">
<FontAwesomeIcon class="lock-icon" :icon="lockIcon" @click="shareItemOptions" /> <lock-icon v-if="isLocked" @click="shareItemOptions" class="lock-icon" size="17"></lock-icon>
<unlock-icon v-if="! isLocked" @click="shareItemOptions" class="lock-icon" size="17"></unlock-icon>
<CopyInput class="copy-sharelink" size="small" :value="fileInfoDetail.shared.link" /> <CopyInput class="copy-sharelink" size="small" :value="fileInfoDetail.shared.link" />
</div> </div>
</li> </li>
@@ -64,6 +69,7 @@
</template> </template>
<script> <script>
import { Edit2Icon, LockIcon, UnlockIcon, ImageIcon, VideoIcon, FolderIcon, FileIcon } from 'vue-feather-icons'
import FilePreview from '@/components/FilesView/FilePreview' import FilePreview from '@/components/FilesView/FilePreview'
import CopyInput from '@/components/Others/Forms/CopyInput' import CopyInput from '@/components/Others/Forms/CopyInput'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
@@ -73,12 +79,20 @@
name: 'FileInfoPanel', name: 'FileInfoPanel',
components: { components: {
FilePreview, FilePreview,
FolderIcon,
UnlockIcon,
VideoIcon,
CopyInput, CopyInput,
ImageIcon,
FileIcon,
Edit2Icon,
LockIcon,
}, },
computed: { computed: {
...mapGetters(['fileInfoDetail', 'permissionOptions']), ...mapGetters(['fileInfoDetail', 'permissionOptions']),
filePreviewIcon() { fileType() {
switch (this.fileInfoDetail.type) { return this.fileInfoDetail.type
/* switch () {
case 'folder': case 'folder':
return 'folder' return 'folder'
break; break;
@@ -94,7 +108,7 @@
case 'file': case 'file':
return 'file-audio' return 'file-audio'
break; break;
} }*/
}, },
sharedInfo() { sharedInfo() {
@@ -117,8 +131,8 @@
return 'download' return 'download'
} }
}, },
lockIcon() { isLocked() {
return this.fileInfoDetail.shared.protected ? 'lock' : 'lock-open' return this.fileInfoDetail.shared.protected
} }
}, },
methods: { methods: {
@@ -135,15 +149,14 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-info-content { .file-info-content {
padding-bottom: 20px; padding-bottom: 20px;
} }
.file-headline { .file-headline {
background: $light_background;
padding: 12px;
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 8px; border-radius: 8px;
@@ -153,37 +166,19 @@
} }
.icon-preview { .icon-preview {
height: 42px;
width: 42px;
border-radius: 8px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
background: white;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
outline: none; outline: none;
border: none; border: none;
/deep/ svg {
@include font-size(22);
path {
fill: $theme;
}
}
&:hover {
.icon path {
fill: $theme;
}
}
} }
.file-info { .file-info {
padding-left: 12px; padding-left: 10px;
width: 100%; width: 100%;
word-break: break-all; word-break: break-all;
@@ -196,7 +191,7 @@
} }
.mimetype { .mimetype {
@include font-size(14); @include font-size(12);
font-weight: 600; font-weight: 600;
color: $theme; color: $theme;
display: block; display: block;
@@ -205,11 +200,10 @@
} }
.list-info { .list-info {
padding-left: 12px;
.list-info-item { .list-info-item {
display: block; display: block;
padding-top: 15px; padding-top: 20px;
&:first-child { &:first-child {
padding-top: 0; padding-top: 0;
@@ -218,14 +212,9 @@
.action-button { .action-button {
cursor: pointer; cursor: pointer;
.icon { .edit-icon {
@include font-size(10);
display: inline-block; display: inline-block;
margin-right: 2px; margin-left: 3px;
path {
fill: $theme;
}
} }
} }
@@ -252,22 +241,10 @@
margin-top: 10px; margin-top: 10px;
.lock-icon { .lock-icon {
@include font-size(10);
display: inline-block; display: inline-block;
width: 10px; width: 15px;
margin-right: 9px; margin-right: 9px;
cursor: pointer; cursor: pointer;
path {
fill: $text;
}
&:hover {
path {
fill: $theme;
}
}
} }
.copy-sharelink { .copy-sharelink {
@@ -278,11 +255,6 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.file-headline { .file-headline {
background: $dark_mode_foreground;
.icon-preview {
background: $dark_mode_background;
}
.file-info { .file-info {
@@ -313,14 +285,10 @@
.lock-icon { .lock-icon {
path {
fill: $dark_mode_text_primary;
}
&:hover { &:hover {
path { path, rect {
fill: $theme; stroke: $theme;
} }
} }
} }
@@ -52,12 +52,12 @@
<!--Shared Icon--> <!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared"> <div v-if="$checkPermission('master') && data.shared" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="share"/> <link-icon size="12" class="shared-icon"></link-icon>
</div> </div>
<!--Participant owner Icon--> <!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared"> <div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="user-edit"/> <user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div> </div>
<!--Filesize--> <!--Filesize-->
@@ -79,6 +79,7 @@
</template> </template>
<script> <script>
import { LinkIcon, UserPlusIcon } from 'vue-feather-icons'
import {debounce} from 'lodash' import {debounce} from 'lodash'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus' import {events} from '@/bus'
@@ -86,6 +87,10 @@
export default { export default {
name: 'FileItemGrid', name: 'FileItemGrid',
props: ['data'], props: ['data'],
components: {
UserPlusIcon,
LinkIcon,
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'FilePreviewType', 'sharedDetail' 'FilePreviewType', 'sharedDetail'
@@ -155,9 +160,9 @@
// Go to folder // Go to folder
if (this.$isThisLocation('public')) { if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false]) this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.data, false]) this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
} }
} }
@@ -187,11 +192,11 @@
} }
if (this.isFolder) { if (this.isFolder) {
// Go to folder
if (this.$isThisLocation('public')) { if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false]) this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.data, false]) this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
} }
} }
}, },
@@ -224,7 +229,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.show-actions { .show-actions {
cursor: pointer; cursor: pointer;
@@ -240,6 +246,7 @@
} }
.file-wrapper { .file-wrapper {
user-select: none;
position: relative; position: relative;
text-align: center; text-align: center;
display: inline-block; display: inline-block;
@@ -254,9 +261,9 @@
.item-size, .item-size,
.item-length { .item-length {
@include font-size(12); @include font-size(11);
font-weight: 400; font-weight: 400;
color: $text-muted; color: rgba($text, 0.7);
display: inline-block; display: inline-block;
} }
@@ -275,10 +282,10 @@
} }
.shared-icon { .shared-icon {
@include font-size(9); vertical-align: middle;
path { path, circle, line {
fill: $theme; stroke: $theme;
} }
} }
} }
@@ -404,6 +411,55 @@
} }
} }
@media only screen and (max-width: 960px) {
.file-wrapper {
.icon-item {
margin-bottom: 15px;
}
}
}
@media only screen and (max-width: 690px) {
.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: .9;
max-height: 30px;
}
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.file-wrapper { .file-wrapper {
@@ -51,12 +51,12 @@
<!--Shared Icon--> <!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared"> <div v-if="$checkPermission('master') && data.shared" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="share"/> <link-icon size="12" class="shared-icon"></link-icon>
</div> </div>
<!--Participant owner Icon--> <!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared"> <div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="user-edit"/> <user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div> </div>
<!--Filesize and timestamp--> <!--Filesize and timestamp-->
@@ -80,6 +80,7 @@
</template> </template>
<script> <script>
import { LinkIcon, UserPlusIcon } from 'vue-feather-icons'
import {debounce} from 'lodash' import {debounce} from 'lodash'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus' import {events} from '@/bus'
@@ -87,6 +88,10 @@
export default { export default {
name: 'FileItemList', name: 'FileItemList',
props: ['data'], props: ['data'],
components: {
UserPlusIcon,
LinkIcon,
},
computed: { computed: {
...mapGetters(['FilePreviewType']), ...mapGetters(['FilePreviewType']),
isFolder() { isFolder() {
@@ -164,9 +169,9 @@
// Go to folder // Go to folder
if (this.$isThisLocation('public')) { if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false]) this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.data, false]) this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
} }
} }
@@ -194,9 +199,9 @@
if (this.isFolder) { if (this.isFolder) {
if (this.$isThisLocation('public')) { if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false]) this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.data, false]) this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
} }
} }
}, },
@@ -229,9 +234,11 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-wrapper { .file-wrapper {
user-select: none;
position: relative; position: relative;
&:hover { &:hover {
@@ -278,19 +285,19 @@
} }
.shared-icon { .shared-icon {
@include font-size(9); vertical-align: middle;
path { path, circle, line {
fill: $theme; stroke: $theme;
} }
} }
} }
.item-size, .item-size,
.item-length { .item-length {
@include font-size(12); @include font-size(11);
font-weight: 400; font-weight: 400;
color: $text-muted; color: rgba($text, 0.7);
} }
.name { .name {
@@ -412,6 +419,7 @@
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.file-wrapper { .file-wrapper {
.icon-item .file-icon { .icon-item .file-icon {
@@ -437,8 +445,16 @@
} }
} }
.item-name .name { .item-name {
color: $dark_mode_text_primary;
.name {
color: $dark_mode_text_primary;
}
.item-size,
.item-length {
color: $dark_mode_text_secondary;
}
} }
} }
} }
@@ -26,7 +26,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.preview { .preview {
width: 100%; width: 100%;
@@ -1,24 +1,37 @@
<template> <template>
<button class="mobile-action-button"> <button class="mobile-action-button">
<FontAwesomeIcon class="icon" :icon="icon"></FontAwesomeIcon> <div class="flex">
<span class="label"> <folder-plus-icon v-if="icon === 'folder-plus'" size="15" class="icon"></folder-plus-icon>
<slot></slot> <list-icon v-if="icon === 'th-list'" size="15" class="icon"></list-icon>
</span> <trash-icon v-if="icon === 'trash'" size="15" class="icon"></trash-icon>
<grid-icon v-if="icon === 'th'" size="15" class="icon"></grid-icon>
<span class="label">
<slot></slot>
</span>
</div>
</button> </button>
</template> </template>
<script> <script>
import { FolderPlusIcon, ListIcon, GridIcon, TrashIcon } from 'vue-feather-icons'
export default { export default {
name: 'MobileActionButton', name: 'MobileActionButton',
props: [ props: [
'icon' 'icon'
], ],
components: {
FolderPlusIcon,
TrashIcon,
ListIcon,
GridIcon,
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-action-button { .mobile-action-button {
background: $light_background; background: $light_background;
@@ -28,6 +41,11 @@
cursor: pointer; cursor: pointer;
border: none; border: none;
.flex {
display: flex;
align-items: center;
}
.icon { .icon {
margin-right: 10px; margin-right: 10px;
@include font-size(14); @include font-size(14);
@@ -44,8 +62,8 @@
.mobile-action-button { .mobile-action-button {
background: $dark_mode_foreground; background: $dark_mode_foreground;
.icon path { path, line, polyline, rect, circle {
fill: $theme; stroke: $theme;
} }
.label { .label {
@@ -1,31 +1,29 @@
<template> <template>
<button class="mobile-action-button"> <button class="mobile-action-button">
<FontAwesomeIcon class="icon" :icon="icon"></FontAwesomeIcon> <div class="flex">
<label label="file" class="label button file-input button-base"> <upload-cloud-icon class="icon" size="15"></upload-cloud-icon>
<slot></slot> <label label="file" class="label button file-input button-base">
<input <slot></slot>
accept="*" <input
v-show="false" @change="emmitFiles"
@change="emmitFiles" v-show="false"
id="file" id="file"
type="file" type="file"
name="files[]" name="files[]"
multiple multiple
/> />
</label> </label>
</div>
</button> </button>
</template> </template>
<script> <script>
import { UploadCloudIcon } from 'vue-feather-icons'
export default { export default {
name: 'MobileActionButtonUpload', name: 'MobileActionButtonUpload',
props: [ components: {
'icon' UploadCloudIcon,
],
data() {
return {
files: undefined
}
}, },
methods: { methods: {
emmitFiles(e) { emmitFiles(e) {
@@ -36,8 +34,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-action-button { .mobile-action-button {
background: $light_background; background: $light_background;
@@ -47,12 +45,19 @@
cursor: pointer; cursor: pointer;
border: none; border: none;
.flex {
display: flex;
align-items: center;
}
.icon { .icon {
margin-right: 8px; vertical-align: middle;
margin-right: 10px;
@include font-size(14); @include font-size(14);
} }
.label { .label {
vertical-align: middle;
@include font-size(14); @include font-size(14);
font-weight: 700; font-weight: 700;
color: $text; color: $text;
@@ -63,8 +68,8 @@
.mobile-action-button { .mobile-action-button {
background: $dark_mode_foreground; background: $dark_mode_foreground;
.icon path { path, line, polyline, rect, circle {
fill: $theme; stroke: $theme;
} }
.label { .label {
@@ -1,22 +1,22 @@
<template> <template>
<div id="mobile-actions-wrapper" v-if="$isMinimalScale()"> <div id="mobile-actions-wrapper">
<!--Actions for trash location with MASTER permission---> <!--Actions for trash location with MASTER permission--->
<div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="mobile-actions"> <div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="mobile-actions">
<MobileActionButton @click.native="switchPreview" :icon="previewIcon"> <MobileActionButton @click.native="switchPreview" :icon="previewIcon">
{{ previewText }} {{ previewText }}
</MobileActionButton> </MobileActionButton>
<MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash-alt"> <MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash">
{{ $t('context_menu.empty_trash') }} {{ $t('context_menu.empty_trash') }}
</MobileActionButton> </MobileActionButton>
</div> </div>
<!--ContextMenu for Base location with MASTER permission--> <!--ContextMenu for Base location with MASTER permission-->
<div v-if="$isThisLocation(['base', 'shared', 'public']) && $checkPermission(['master', 'editor'])" class="mobile-actions"> <div v-if="$isThisLocation(['base', 'public']) && $checkPermission(['master', 'editor'])" class="mobile-actions">
<MobileActionButton @click.native="createFolder" icon="folder-plus"> <MobileActionButton @click.native="createFolder" icon="folder-plus">
{{ $t('context_menu.add_folder') }} {{ $t('context_menu.add_folder') }}
</MobileActionButton> </MobileActionButton>
<MobileActionButtonUpload @input.native="$uploadFiles" icon="upload"> <MobileActionButtonUpload>
{{ $t('context_menu.upload') }} {{ $t('context_menu.upload') }}
</MobileActionButtonUpload> </MobileActionButtonUpload>
<MobileActionButton @click.native="switchPreview" :icon="previewIcon"> <MobileActionButton @click.native="switchPreview" :icon="previewIcon">
@@ -25,7 +25,7 @@
</div> </div>
<!--ContextMenu for Base location with VISITOR permission--> <!--ContextMenu for Base location with VISITOR permission-->
<div v-if="$isThisLocation(['base', 'shared', 'public']) && $checkPermission('visitor')" class="mobile-actions"> <div v-if="($isThisLocation(['base', 'shared', 'public']) && $checkPermission('visitor')) || ($isThisLocation(['latest', 'shared']) && $checkPermission('master'))" class="mobile-actions">
<MobileActionButton @click.native="switchPreview" :icon="previewIcon"> <MobileActionButton @click.native="switchPreview" :icon="previewIcon">
{{ previewText }} {{ previewText }}
</MobileActionButton> </MobileActionButton>
@@ -81,10 +81,11 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#mobile-actions-wrapper { #mobile-actions-wrapper {
display: none;
background: white; background: white;
position: sticky; position: sticky;
top: 35px; top: 35px;
@@ -98,6 +99,13 @@
overflow-x: auto; overflow-x: auto;
} }
@media only screen and (max-width: 960px) {
#mobile-actions-wrapper {
display: block;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
#mobile-actions-wrapper { #mobile-actions-wrapper {
background: $dark_mode_background; background: $dark_mode_background;
+262 -74
View File
@@ -9,79 +9,205 @@
> >
<div class="menu-wrapper"> <div class="menu-wrapper">
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="fileInfoDetail" info="metadata"/>
<!--Mobile for trash location--> <!--Mobile for trash location-->
<ul v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options"> <div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options">
<li class="menu-option" @click="$store.dispatch('restoreItem', fileInfoDetail)" v-if="fileInfoDetail">
{{ $t('context_menu.restore') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="$store.dispatch('restoreItem', fileInfoDetail)" v-if="fileInfoDetail">
<li class="menu-option" @click="downloadItem" v-if="! isFolder"> <div class="icon">
{{ $t('context_menu.download') }} <life-buoy-icon size="17"></life-buoy-icon>
</li> </div>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail"> <div class="text-label">
{{ $t('context_menu.delete') }} {{ $t('context_menu.restore') }}
</li> </div>
</ul> </li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group" v-if="! isFolder">
<li class="menu-option" @click="downloadItem">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location--> <!--Mobile for Base location-->
<ul v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options"> <div v-if="$isThisLocation(['shared']) && $checkPermission('master')" 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') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && isFolder">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail"> <div class="icon">
{{ $t('context_menu.rename') }} <star-icon size="17"></star-icon>
</li> </div>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail"> <div class="text-label">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }} {{ isInFavourites ? $t('context_menu.remove_from_favourites') :
</li> $t('context_menu.add_to_favourites') }}
<li class="menu-option" @click="downloadItem" v-if="! isFolder"> </div>
{{ $t('context_menu.download') }} </li>
</li> </ul>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
</ul> <div class="icon">
<edit-2-icon size="17"></edit-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.rename') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location--> <!--Mobile for Base location-->
<ul v-if="$isThisLocation(['base']) && $checkPermission('master')" class="menu-options"> <div v-if="$isThisLocation(['base', 'latest']) && $checkPermission('master')" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && isFolder"> <ul class="menu-option-group" v-if="fileInfoDetail && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }} <li class="menu-option" @click="addToFavourites">
</li> <div class="icon">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail"> <star-icon size="17"></star-icon>
{{ $t('context_menu.rename') }} </div>
</li> <div class="text-label">
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail"> {{ isInFavourites ? $t('context_menu.remove_from_favourites') :
{{ $t('context_menu.move') }} $t('context_menu.add_to_favourites') }}
</li> </div>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail"> </li>
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }} </ul>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder"> <ul class="menu-option-group">
{{ $t('context_menu.download') }} <li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
</li> <div class="icon">
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail"> <edit-2-icon size="17"></edit-2-icon>
{{ $t('context_menu.delete') }} </div>
</li> <div class="text-label">
</ul> {{ $t('context_menu.rename') }}
</div>
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail">
<div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location with EDITOR permission--> <!--Mobile for Base location with EDITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options"> <div v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
{{ $t('context_menu.rename') }} <ul class="menu-option-group">
</li> <li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail"> <div class="icon">
{{ $t('context_menu.move') }} <edit-2-icon size="17"></edit-2-icon>
</li> </div>
<li class="menu-option" @click="downloadItem" v-if="! isFolder"> <div class="text-label">
{{ $t('context_menu.download') }} {{ $t('context_menu.rename') }}
</li> </div>
</ul> </li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail">
<div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location with VISITOR permission--> <!--Mobile for Base location with VISITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options"> <div v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options">
<li class="menu-option" @click="downloadItem" v-if="! isFolder"> <ul class="menu-option-group">
{{ $t('context_menu.download') }} <li class="menu-option" @click="downloadItem" v-if="! isFolder">
</li> <div class="icon">
</ul> <download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</transition> </transition>
@@ -92,11 +218,37 @@
</template> </template>
<script> <script>
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
TrashIcon,
StarIcon,
LinkIcon,
EyeIcon,
} from 'vue-feather-icons'
import {events} from '@/bus' import {events} from '@/bus'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
export default { export default {
name: 'MobileMenu', name: 'MobileMenu',
components: {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
ThumbnailItem,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: { computed: {
...mapGetters(['fileInfoDetail', 'app']), ...mapGetters(['fileInfoDetail', 'app']),
isInFavourites() { isInFavourites() {
@@ -194,10 +346,25 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.menu-option {
display: flex;
align-items: center;
.icon {
margin-right: 20px;
line-height: 0;
}
.text-label {
@include font-size(16);
}
}
.vignette { .vignette {
background: rgba(0, 0, 0, 0.15); background: rgba(0, 0, 0, 0.35);
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@@ -215,29 +382,46 @@
right: 0; right: 0;
z-index: 99; z-index: 99;
overflow: hidden; overflow: hidden;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
&.showed { &.showed {
display: block; display: block;
} }
.item-thumbnail {
padding: 20px 20px 10px;
margin-bottom: 0px;
}
.menu-options { .menu-options {
margin-top: 10px; margin-top: 10px;
box-shadow: $shadow;
background: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
list-style: none; list-style: none;
width: 100%; width: 100%;
.menu-option-group {
padding: 5px 0;
border-bottom: 1px solid $light_mode_border;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
.menu-option { .menu-option {
font-weight: 700; font-weight: 700;
letter-spacing: 0.15px; letter-spacing: 0.15px;
@include font-size(15); @include font-size(14);
cursor: pointer; cursor: pointer;
width: 100%; width: 100%;
padding: 20px 10px; padding: 17px 20px;
text-align: center; text-align: center;
border-bottom: 1px solid $light_mode_border;
&:last-child { &:last-child {
border: none; border: none;
@@ -253,12 +437,16 @@
} }
.options { .options {
background: $dark_mode_background;
.menu-options { .menu-options {
background: $dark_mode_background; background: $dark_mode_background;
.menu-option { .menu-option-group {
border-color: $dark_mode_border_color; border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
} }
} }
@@ -1,13 +1,9 @@
<template> <template>
<div class="mobile-toolbar" v-if="$isMinimalScale()"> <div class="mobile-toolbar">
<!-- Go back--> <!-- Go back-->
<div @click="goBack" class="go-back-button"> <div @click="goBack" class="go-back-button">
<FontAwesomeIcon <chevron-left-icon size="17" :class="{'is-visible': browseHistory.length > 1}" class="icon-back"></chevron-left-icon>
:class="{'is-visible': browseHistory.length > 0}"
class="icon-back"
icon="chevron-left"
></FontAwesomeIcon>
</div> </div>
<!--Folder Title--> <!--Folder Title-->
@@ -15,8 +11,8 @@
<!--More Actions--> <!--More Actions-->
<div class="more-actions-button"> <div class="more-actions-button">
<div class="tap-area" @click="showSidebarMenu" v-if="$checkPermission('master')"> <div class="tap-area" @click="showMobileNavigation" v-if="$checkPermission('master')">
<FontAwesomeIcon icon="bars" v-if="isSmallAppSize"></FontAwesomeIcon> <menu-icon size="17"></menu-icon>
</div> </div>
</div> </div>
</div> </div>
@@ -26,15 +22,19 @@
import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload' import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload'
import ToolbarButton from '@/components/FilesView/ToolbarButton' import ToolbarButton from '@/components/FilesView/ToolbarButton'
import SearchBar from '@/components/FilesView/SearchBar' import SearchBar from '@/components/FilesView/SearchBar'
import { MenuIcon, ChevronLeftIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {events} from '@/bus' import {events} from '@/bus'
import {last} from 'lodash'
export default { export default {
name: 'MobileToolBar', name: 'MobileToolBar',
components: { components: {
ToolbarButtonUpload, ToolbarButtonUpload,
ChevronLeftIcon,
ToolbarButton, ToolbarButton,
SearchBar SearchBar,
MenuIcon,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
@@ -49,36 +49,30 @@
directoryName() { directoryName() {
return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name
}, },
previousFolder() {
const length = this.browseHistory.length - 2
return this.browseHistory[length] ? this.browseHistory[length] : this.homeDirectory
},
isSmallAppSize() { isSmallAppSize() {
return this.appSize === 'small' return this.appSize === 'small'
} }
}, },
data() {
return {
isSidebarMenu: false,
}
},
methods: { methods: {
showSidebarMenu() { showMobileNavigation() {
this.isSidebarMenu = ! this.isSidebarMenu events.$emit('show:mobile-navigation')
events.$emit('show:sidebar')
}, },
goBack() { goBack() {
if (this.previousFolder.location === 'trash-root') { let previousFolder = last(this.browseHistory)
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash') this.$store.dispatch('getTrash')
this.$store.commit('FLUSH_BROWSER_HISTORY')
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
} else { } else {
if ( this.$isThisLocation('public') ) { if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.previousFolder, true]) this.$store.dispatch('browseShared', [{folder: previousFolder, back: true, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.previousFolder, true]) this.$store.dispatch('getFolder', [{folder: previousFolder, back: true, init: false}])
} }
} }
}, },
@@ -93,13 +87,13 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-toolbar { .mobile-toolbar {
background: white; background: white;
text-align: center; text-align: center;
display: flex; display: none;
padding: 10px 0; padding: 10px 0;
position: sticky; position: sticky;
top: 0; top: 0;
@@ -121,6 +115,7 @@
cursor: pointer; cursor: pointer;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
margin-top: -2px;
&.is-visible { &.is-visible {
opacity: 1; opacity: 1;
@@ -154,10 +149,21 @@
position: absolute; position: absolute;
right: -10px; right: -10px;
top: -20px; top: -20px;
path, line, polyline, rect, circle {
stroke: $text;
}
} }
} }
} }
@media only screen and (max-width: 960px) {
.mobile-toolbar {
display: flex;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.mobile-toolbar { .mobile-toolbar {
@@ -167,8 +173,11 @@
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
} }
.more-actions-button svg path { .more-actions-button .tap-area {
fill: $dark_mode_text_primary;
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
} }
} }
} }
@@ -12,7 +12,8 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.progress-bar { .progress-bar {
width: 100%; width: 100%;
+54 -19
View File
@@ -1,5 +1,11 @@
<template> <template>
<div class="search-bar"> <div class="search-bar">
<div class="icon" v-if="!isQuery">
<search-icon size="19"></search-icon>
</div>
<div class="icon" v-if="isQuery" @click="resetQuery">
<x-icon class="pointer" size="19"></x-icon>
</div>
<input <input
v-model="query" v-model="query"
class="query" class="query"
@@ -7,22 +13,21 @@
name="query" name="query"
:placeholder="$t('inputs.placeholder_search_files')" :placeholder="$t('inputs.placeholder_search_files')"
/> />
<div class="icon" v-if="!isQuery">
<FontAwesomeIcon icon="search"></FontAwesomeIcon>
</div>
<div class="icon" v-if="isQuery" @click="resetQuery">
<FontAwesomeIcon icon="times" class="pointer"></FontAwesomeIcon>
</div>
</div> </div>
</template> </template>
<script> <script>
import { SearchIcon, XIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {debounce} from 'lodash' import {debounce} from 'lodash'
import {events} from '@/bus' import {events} from '@/bus'
export default { export default {
name: 'SearchBar', name: 'SearchBar',
components: {
SearchIcon,
XIcon,
},
computed: { computed: {
...mapGetters(['currentFolder']), ...mapGetters(['currentFolder']),
isQuery() { isQuery() {
@@ -52,9 +57,9 @@
// Get back after delete query to previosly folder // Get back after delete query to previosly folder
if ( this.$isThisLocation('public') ) { if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.currentFolder, true]) this.$store.dispatch('browseShared', [{folder: this.currentFolder, back: true, init: false}])
} else { } else {
this.$store.dispatch('getFolder', [this.currentFolder, true]) this.$store.dispatch('getFolder', [{folder: this.currentFolder, back: true, init: false}])
} }
} }
@@ -69,20 +74,20 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.search-bar { .search-bar {
position: relative; position: relative;
input { input {
//width: 100%; background: transparent;
background: $light_background;
border-radius: 8px; border-radius: 8px;
outline: 0; outline: 0;
padding: 9px 20px; padding: 9px 20px 9px 43px;
font-weight: 100; font-weight: 400;
@include font-size(16); @include font-size(16);
min-width: 380px; min-width: 175px;
transition: 0.15s all ease; transition: 0.15s all ease;
border: 1px solid white; border: 1px solid white;
-webkit-appearance: none; -webkit-appearance: none;
@@ -90,7 +95,7 @@
&::placeholder { &::placeholder {
color: $text; color: $text;
@include font-size(14); @include font-size(14);
font-weight: 400; font-weight: 500;
} }
&:focus { &:focus {
@@ -108,8 +113,8 @@
.icon { .icon {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; left: 0;
padding: 10px 15px; padding: 11px 15px;
.pointer { .pointer {
cursor: pointer; cursor: pointer;
@@ -117,11 +122,41 @@
} }
} }
@media only screen and (max-width: 1024px) {
.search-bar input {
max-width: 190px;
padding-right: 0;
}
}
@media only screen and (max-width: 690px) {
.search-bar {
input {
min-width: initial;
width: 100%;
padding: 9px 20px 9px 30px;
&:focus {
border: 1px solid transparent;
box-shadow: none;
}
}
.icon {
padding: 11px 15px 11px 0;
}
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.search-bar { .search-bar {
input { input {
background: $dark_mode_foreground; border-color: transparent;
border-color: $dark_mode_foreground; color: $dark_mode_text_primary;
&::placeholder { &::placeholder {
color: $dark_mode_text_secondary; color: $dark_mode_text_secondary;
@@ -11,7 +11,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#loading-bar-spinner.spinner { #loading-bar-spinner.spinner {
left: 50%; left: 50%;
@@ -1,18 +1,36 @@
<template> <template>
<button class="button" :title="action"> <button class="button" :title="action">
<FontAwesomeIcon class="icon" :icon="source"></FontAwesomeIcon> <corner-down-right-icon v-if="source === 'move'" size="19"></corner-down-right-icon>
<folder-plus-icon v-if="source === 'folder-plus'" size="19"></folder-plus-icon>
<trash-2-icon v-if="source === 'trash'" size="19"></trash-2-icon>
<list-icon v-if="source === 'th-list'" size="19"></list-icon>
<info-icon v-if="source === 'info'" size="19"></info-icon>
<grid-icon v-if="source === 'th'" size="19"></grid-icon>
<link-icon v-if="source === 'share'" size="19"></link-icon>
</button> </button>
</template> </template>
<script> <script>
import {FolderPlusIcon, Trash2Icon, GridIcon, ListIcon, InfoIcon, CornerDownRightIcon, LinkIcon} from 'vue-feather-icons'
export default { export default {
name: 'ToolbarButton', name: 'ToolbarButton',
props: ['source', 'action'] props: ['source', 'action'],
components: {
CornerDownRightIcon,
FolderPlusIcon,
Trash2Icon,
ListIcon,
GridIcon,
InfoIcon,
LinkIcon,
},
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button { .button {
height: 42px; height: 42px;
@@ -22,42 +40,36 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
background: $light_background;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
outline: none; outline: none;
border: none; border: none;
@include transition(150ms);
.icon { background: transparent;
@include font-size(16);
}
&:hover { &:hover {
background: rgba($theme, .1); background: $light_background;
/deep/ svg path { path, line, polyline, rect, circle {
@include transition; @include transition(150ms);
fill: $theme; stroke: $theme;
}
}
&.active {
background: rgba($theme, .1);
/deep/ svg path {
fill: $theme;
} }
} }
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.button {
background: $dark_mode_foreground;
}
.icon path { .button {
fill: $dark_mode_text_primary; background: transparent;
&:hover {
background: $dark_mode_foreground;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
} }
} }
</style> </style>
@@ -1,6 +1,6 @@
<template> <template>
<label label="file" class="button file-input"> <label label="file" class="button file-input">
<FontAwesomeIcon class="icon" :icon="source"></FontAwesomeIcon> <upload-cloud-icon size="17"></upload-cloud-icon>
<input <input
@change="emmitFiles" @change="emmitFiles"
v-show="false" v-show="false"
@@ -8,15 +8,19 @@
type="file" type="file"
name="files[]" name="files[]"
multiple multiple
:disabled="$isThisLocation(['trash', 'trash-root'])"
/> />
</label> </label>
</template> </template>
<script> <script>
import { UploadCloudIcon } from 'vue-feather-icons'
export default { export default {
name: 'ToolbarButtonUpload', name: 'ToolbarButtonUpload',
props: ['source', 'action'], props: ['action'],
components: {
UploadCloudIcon,
},
methods: { methods: {
emmitFiles(e) { emmitFiles(e) {
this.$uploadFiles(e.target.files) this.$uploadFiles(e.target.files)
@@ -26,7 +30,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button { .button {
height: 42px; height: 42px;
@@ -36,7 +41,6 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
background: $light_background;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
@@ -44,26 +48,27 @@
border: none; border: none;
&:hover { &:hover {
background: rgba($theme, .1); background: $light_background;
/deep/ svg path { path, line, polyline, rect, circle {
@include transition; @include transition(150ms);
fill: $theme; stroke: $theme;
} }
} }
.icon {
@include font-size(16);
}
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.button {
background: $dark_mode_foreground;
}
.icon path { .button {
fill: $dark_mode_text_primary; background: transparent;
&:hover {
background: $dark_mode_foreground;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
} }
} }
</style> </style>
@@ -25,7 +25,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.info-panel-enter-active, .info-panel-enter-active,
.info-panel-leave-active { .info-panel-leave-active {
@@ -0,0 +1,115 @@
<template>
<ul class="link-group">
<router-link :to="{name: link.routeName}" v-for="(link, i) in navigation" :key="i" :class="link.icon" class="link-item" @click.native="$emit('menu', link.icon)">
<div class="menu-icon">
<hard-drive-icon v-if="link.icon === 'hard-drive'" size="17"></hard-drive-icon>
<share-icon v-if="link.icon === 'share'" size="17"></share-icon>
<trash2-icon v-if="link.icon === 'trash'" size="17"></trash2-icon>
<power-icon v-if="link.icon === 'power'" size="17"></power-icon>
<settings-icon v-if="link.icon === 'settings'" size="17"></settings-icon>
<upload-cloud-icon v-if="link.icon === 'latest'" size="17"></upload-cloud-icon>
</div>
<b class="menu-link">
<span>{{ link.title }}</span>
<chevron-right-icon size="15" class="arrow-right"></chevron-right-icon>
</b>
</router-link>
</ul>
</template>
<script>
import {
ChevronRightIcon,
UploadCloudIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
ChevronRightIcon,
UploadCloudIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
},
props: [
'navigation'
],
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.link-item {
display: flex;
text-decoration: none;
padding: 17px 0;
width: 100%;
&.power {
.menu-icon {
path, line, polyline, rect, circle {
stroke: $red;
}
}
.menu-link {
color: $red;
}
}
.menu-icon {
display: block;
margin-right: 20px;
svg {
margin-top: -1px;
vertical-align: middle;
}
path, line, polyline, rect, circle {
stroke: $text;
}
}
.menu-link {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: $text;
span {
@include font-size(14);
}
}
}
@media (prefers-color-scheme: dark) {
.link-item {
.menu-icon {
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
.menu-link {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -0,0 +1,108 @@
<template>
<header class="mobile-header">
<!-- Go back-->
<div @click="goBack" class="go-back">
<chevron-left-icon size="17" class="icon"></chevron-left-icon>
</div>
<!--Folder Title-->
<div class="location-name">{{ $router.currentRoute.meta.title }}</div>
<!--More Actions-->
<div @click="showMobileNavigation" class="mobile-menu">
<menu-icon size="17" class="icon"></menu-icon>
</div>
</header>
</template>
<script>
import {events} from '@/bus'
import {
ChevronLeftIcon,
MenuIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
ChevronLeftIcon,
MenuIcon,
},
methods: {
showMobileNavigation() {
events.$emit('show:mobile-navigation')
},
goBack() {
this.$router.back();
}
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-header {
padding: 10px 15px;
text-align: center;
background: white;
position: sticky;
display: none;
z-index: 6;
top: 0;
> div {
flex-grow: 1;
align-self: center;
white-space: nowrap;
}
.go-back {
text-align: left;
}
.location-name {
line-height: 1;
text-align: center;
width: 100%;
vertical-align: middle;
@include font-size(15);
color: $text;
font-weight: 700;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.mobile-menu {
text-align: right;
}
.icon {
vertical-align: middle;
margin-top: -4px;
}
}
@media only screen and (max-width: 690px) {
.mobile-header {
display: flex;
margin-bottom: 25px;
}
}
@media (prefers-color-scheme: dark) {
.mobile-header {
background: $dark_mode_background;
.location-name {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -15,7 +15,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.action-button { .action-button {
cursor: pointer; cursor: pointer;
@@ -0,0 +1,63 @@
<template>
<div @click="fileViewClick"
@contextmenu.prevent.capture="contextMenu($event, undefined)"
id="files-view">
<ContextMenu/>
<DesktopToolbar/>
<FileBrowser/>
</div>
</template>
<script>
import DesktopToolbar from '@/components/FilesView/DesktopToolbar'
import FileBrowser from '@/components/FilesView/FileBrowser'
import ContextMenu from '@/components/FilesView/ContextMenu'
import {ResizeSensor} from 'css-element-queries'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilesView',
components: {
DesktopToolbar,
FileBrowser,
ContextMenu,
},
computed: {
...mapGetters(['config']),
},
methods: {
fileViewClick() {
events.$emit('contextMenu:hide')
},
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
},
},
}
</script>
<style lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#files-view {
font-family: 'Nunito', sans-serif;
font-size: 16px;
width: 100%;
height: 100%;
position: relative;
min-width: 320px;
overflow-x: hidden;
padding-left: 15px;
padding-right: 15px;
}
@media only screen and (max-width: 690px) {
#files-view {
padding-left: 0;
padding-right: 0;
}
}
</style>
@@ -2,15 +2,22 @@
<div class="inline-wrapper icon-append copy-input" :class="size" @click="copyUrl"> <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> <input ref="sel" :value="value" id="link-input" type="text" class="input-text" readonly>
<div class="icon"> <div class="icon">
<FontAwesomeIcon :icon="isCopiedLink ? 'check' : 'link'"/> <link-icon v-if="! isCopiedLink" size="14"></link-icon>
<check-icon v-if="isCopiedLink" size="14"></check-icon>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { LinkIcon, CheckIcon } from 'vue-feather-icons'
export default { export default {
name: 'CopyInput', name: 'CopyInput',
props: ['size', 'value'], props: ['size', 'value'],
components: {
CheckIcon,
LinkIcon,
},
data() { data() {
return { return {
isCopiedLink: false, isCopiedLink: false,
@@ -40,8 +47,10 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import "@assets/vue-file-manager/_inapp-forms.scss"; @import "@assets/vue-file-manager/_inapp-forms.scss";
@import "@assets/vue-file-manager/_forms.scss";
// Single page // Single page
.copy-input { .copy-input {
@@ -52,7 +61,6 @@
.icon { .icon {
padding: 8px 10px; padding: 8px 10px;
@include font-size(11);
} }
} }
@@ -81,9 +89,6 @@
.copy-input { .copy-input {
input { input {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
&:disabled {
}
} }
} }
} }
@@ -7,7 +7,8 @@
<!--If is selected--> <!--If is selected-->
<div class="selected" v-if="selected"> <div class="selected" v-if="selected">
<div class="option-icon" v-if="selected.icon"> <div class="option-icon" v-if="selected.icon">
<FontAwesomeIcon :icon="selected.icon" /> <user-icon v-if="selected.icon === 'user'" size="14"></user-icon>
<edit2-icon v-if="selected.icon === 'user-edit'" size="14"></edit2-icon>
</div> </div>
<span class="option-value">{{ selected.label }}</span> <span class="option-value">{{ selected.label }}</span>
</div> </div>
@@ -17,7 +18,7 @@
<span class="option-value placehoder">{{ placeholder }}</span> <span class="option-value placehoder">{{ placeholder }}</span>
</div> </div>
<FontAwesomeIcon icon="chevron-down" class="chevron"/> <chevron-down-icon size="19" class="chevron"></chevron-down-icon>
</div> </div>
<!--Options--> <!--Options-->
@@ -25,7 +26,8 @@
<ul class="input-options" v-if="isOpen"> <ul class="input-options" v-if="isOpen">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in options" :key="i"> <li class="option-item" @click="selectOption(option)" v-for="(option, i) in options" :key="i">
<div class="option-icon" v-if="option.icon"> <div class="option-icon" v-if="option.icon">
<FontAwesomeIcon :icon="option.icon" /> <user-icon v-if="option.icon === 'user'" size="14"></user-icon>
<edit2-icon v-if="option.icon === 'user-edit'" size="14"></edit2-icon>
</div> </div>
<span class="option-value">{{ option.label }}</span> <span class="option-value">{{ option.label }}</span>
</li> </li>
@@ -35,9 +37,16 @@
</template> </template>
<script> <script>
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
export default { export default {
name:'SelectInput', name:'SelectInput',
props: ['options', 'isError', 'default', 'placeholder'], props: ['options', 'isError', 'default', 'placeholder'],
components: {
Edit2Icon,
UserIcon,
ChevronDownIcon
},
data() { data() {
return { return {
selected: undefined, selected: undefined,
@@ -69,7 +78,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.select { .select {
position: relative; position: relative;
@@ -77,7 +87,7 @@
} }
.input-options { .input-options {
background: $light_background; background: $light_mode_input_background;
border-radius: 8px; border-radius: 8px;
position: absolute; position: absolute;
overflow: hidden; overflow: hidden;
@@ -105,7 +115,7 @@
.input-area { .input-area {
justify-content: space-between; justify-content: space-between;
background: $light_background; background: $light_mode_input_background;
border: 1px solid transparent; border: 1px solid transparent;
@include transition(150ms); @include transition(150ms);
align-items: center; align-items: center;
@@ -138,16 +148,22 @@
.option-icon { .option-icon {
width: 20px; width: 20px;
display: inline-block; display: inline-block;
@include font-size(12); @include font-size(10);
svg {
margin-top: -4px;
vertical-align: middle;
}
} }
.option-value { .option-value {
@include font-size(15); @include font-size(14);
font-weight: 700; font-weight: 700;
width: 100%; width: 100%;
vertical-align: middle;
&.placehoder { &.placehoder {
color: $light_text; color: rgba($text, 0.5);
} }
} }
@@ -39,7 +39,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.input-wrapper { .input-wrapper {
display: flex; display: flex;
@@ -0,0 +1,177 @@
<template>
<div class="mobile-main-navigation">
<transition name="context-menu">
<nav v-if="isVisible" class="mobile-navigation">
<!--User Info-->
<div class="user-info">
<UserAvatar size="large" />
<UserHeadline/>
</div>
<!--Navigation-->
<MenuItemList :navigation="navigation" @menu="action" />
</nav>
</transition>
<transition name="fade">
<div v-show="isVisible" class="vignette" @click="closeAndResetContextMenu"></div>
</transition>
</div>
</template>
<script>
import UserHeadline from '@/components/Sidebar/UserHeadline'
import MenuItemList from '@/components/Mobile/MenuItemList'
import UserAvatar from '@/components/Others/UserAvatar'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MenuBar',
components: {
MenuItemList,
UserHeadline,
UserAvatar,
},
computed: {
...mapGetters(['app', 'homeDirectory']),
},
data() {
return {
isVisible: false,
navigation: [
{
icon: 'hard-drive',
title: 'Files',
routeName: 'Files',
},
{
icon: 'latest',
title: 'Recent Uploads',
routeName: 'Files',
},
{
icon: 'share',
title: 'Shared Files',
routeName: 'SharedFiles',
},
{
icon: 'trash',
title: 'Trash',
routeName: 'Trash',
},
{
icon: 'settings',
title: 'Settings',
routeName: 'MobileSettings',
},
{
icon: 'power',
title: 'Log Out',
routeName: 'LogOut',
},
]
}
},
methods: {
action(name) {
if (name === 'latest') {
this.$store.dispatch('getLatest')
}
if (name === 'hard-drive') {
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
}
this.closeAndResetContextMenu()
},
closeAndResetContextMenu() {
this.isVisible = false
events.$emit('hide:mobile-navigation')
}
},
created() {
events.$on('show:mobile-navigation', () => {
this.isVisible = true
})
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-navigation {
padding: 20px;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
min-height: 440px;
max-height: 80%;
overflow-y: auto;
}
.vignette {
background: rgba(0, 0, 0, 0.35);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9;
cursor: pointer;
opacity: 1;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 10px;
}
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) {
.mobile-navigation {
background: $dark_mode_background;
}
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
</style>
+1 -2
View File
@@ -1,7 +1,7 @@
<template> <template>
<PopupWrapper name="move"> <PopupWrapper name="move">
<!--Title--> <!--Title-->
<PopupHeader :title="$t('popup_move_item.title')" /> <PopupHeader :title="$t('popup_move_item.title')" icon="move" />
<!--Content--> <!--Content-->
<PopupContent type="height-limited" v-if="pickedItem"> <PopupContent type="height-limited" v-if="pickedItem">
@@ -123,7 +123,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss";
.item-thumbnail { .item-thumbnail {
margin-bottom: 20px; margin-bottom: 20px;
+13 -5
View File
@@ -36,19 +36,20 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.page-header { .page-header {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 20px 30px;
background: white; background: white;
position: sticky;
top: 0;
z-index: 9; z-index: 9;
max-width: 700px;
width: 100%;
margin: 20px auto 30px;
.title { .title {
@include font-size(22); @include font-size(18);
font-weight: 700; font-weight: 700;
color: $text; color: $text;
} }
@@ -64,6 +65,7 @@
.page-header { .page-header {
padding: 20px 15px; padding: 20px 15px;
margin: 0;
.title { .title {
@include font-size(18); @include font-size(18);
@@ -71,6 +73,12 @@
} }
} }
@media only screen and (max-width: 690px) {
.page-header {
display: none;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.page-header { .page-header {
@@ -11,7 +11,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.actions { .actions {
padding: 20px; padding: 20px;
@@ -14,7 +14,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup-content { .popup-content {
@@ -1,26 +1,58 @@
<template> <template>
<div class="popup-header"> <div class="popup-header">
<h1 class="title">{{ title }}</h1> <div class="icon">
<corner-down-right-icon v-if="icon === 'move'" size="15" class="title-icon"></corner-down-right-icon>
<link-icon v-if="icon === 'share'" size="17" class="title-icon"></link-icon>
</div>
<div class="label">
<h1 class="title">{{ title }}</h1>
<x-icon @click="closePopup" size="22" class="close-icon"></x-icon>
</div>
</div> </div>
</template> </template>
<script> <script>
import { CornerDownRightIcon, LinkIcon, XIcon } from 'vue-feather-icons'
import {events} from '@/bus'
export default { export default {
name: 'PopupHeader', name: 'PopupHeader',
props: [ props: [
'title' 'title', 'icon'
] ],
components: {
CornerDownRightIcon,
LinkIcon,
XIcon,
},
methods: {
closePopup() {
events.$emit('popup:close')
}
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup-header { .popup-header {
padding: 20px; padding: 20px;
display: flex;
align-items: center;
.icon {
margin-right: 10px;
line-height: 0;
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.title { .title {
@include font-size(18); @include font-size(17);
font-weight: 700; font-weight: 700;
color: $text; color: $text;
} }
@@ -30,6 +62,27 @@
color: #8b8f9a; color: #8b8f9a;
margin-top: 5px; margin-top: 5px;
} }
.label {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
.close-icon {
padding: 1px 4px;
border-radius: 6px;
&:hover {
background: $light_background;
line {
stroke: $theme;
}
}
cursor: pointer;
}
}
} }
.small { .small {
@@ -38,6 +91,10 @@
} }
} }
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.popup-header { .popup-header {
.title { .title {
@@ -1,6 +1,6 @@
<template> <template>
<transition name="popup"> <transition name="popup">
<div class="popup" @click.self="closePopup" v-show="isVisibleWrapper"> <div class="popup" @click.self="closePopup" v-if="isVisibleWrapper">
<div class="popup-wrapper"> <div class="popup-wrapper">
<slot></slot> <slot></slot>
</div> </div>
@@ -46,7 +46,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup { .popup {
position: absolute; position: absolute;
@@ -129,6 +130,15 @@
} }
} }
@media only screen and (max-width: 690px) {
.popup-wrapper {
left: 15px;
right: 15px;
padding: 25px 15px;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.popup-wrapper { .popup-wrapper {
background: $dark_mode_background; background: $dark_mode_background;
@@ -1,7 +1,7 @@
<template> <template>
<PopupWrapper name="share-create"> <PopupWrapper name="share-create">
<!--Title--> <!--Title-->
<PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" /> <PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" icon="share" />
<!--Content--> <!--Content-->
<PopupContent> <PopupContent>
@@ -181,8 +181,6 @@
// Restore data // Restore data
setTimeout(() => { setTimeout(() => {
this.isGeneratedShared = false
this.shareLink = undefined
this.shareOptions = { this.shareOptions = {
permission: undefined, permission: undefined,
password: undefined, password: undefined,
@@ -190,6 +188,8 @@
type: undefined, type: undefined,
unique_id: undefined, unique_id: undefined,
} }
this.isGeneratedShared = false
this.shareLink = undefined
}, 150) }, 150)
}) })
} }
@@ -197,8 +197,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss"; @import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.input-wrapper { .input-wrapper {
+2 -2
View File
@@ -1,7 +1,7 @@
<template> <template>
<PopupWrapper name="share-edit"> <PopupWrapper name="share-edit">
<!--Title--> <!--Title-->
<PopupHeader :title="$t('popup_share_edit.title')" /> <PopupHeader :title="$t('popup_share_edit.title')" icon="share" />
<!--Content--> <!--Content-->
<PopupContent v-if="pickedItem && pickedItem.shared"> <PopupContent v-if="pickedItem && pickedItem.shared">
@@ -243,8 +243,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss"; @import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.input-wrapper { .input-wrapper {
+12 -5
View File
@@ -11,17 +11,24 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.text-label { .text-label {
@include font-size(10); padding-left: 25px;
color: $theme; @include font-size(12);
text-transform: uppercase; color: #AFAFAF;
font-weight: 900; font-weight: 700;
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
} }
@media only screen and (max-width: 1024px) {
.text-label {
padding-left: 20px;
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.text-label { .text-label {
color: $theme; color: $theme;
@@ -11,14 +11,14 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.theme-label { .theme-label {
@include font-size(14); @include font-size(14);
color: $theme; color: $theme;
text-transform: uppercase; font-weight: 600;
font-weight: 900;
display: block; display: block;
margin-bottom: 5px; margin-bottom: 20px;
} }
</style> </style>
@@ -61,7 +61,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-item { .file-item {
display: flex; display: flex;
@@ -104,7 +105,7 @@
.icon-item { .icon-item {
position: relative; position: relative;
min-width: 40px; min-width: 52px;
text-align: center; text-align: center;
line-height: 0; line-height: 0;
@@ -150,8 +151,8 @@
user-select: none; user-select: none;
max-width: 100%; max-width: 100%;
border-radius: 5px; border-radius: 5px;
width: 36px; width: 52px;
height: 36px; height: 52px;
} }
} }
} }
@@ -159,7 +160,7 @@
.small { .small {
.file-item { .file-item {
padding: 0 15px; padding: 0 15px;
margin-bottom: 25px; margin-bottom: 20px;
} }
} }
+38 -51
View File
@@ -2,9 +2,10 @@
<!--Folder Icon--> <!--Folder Icon-->
<div class="folder-item-wrapper"> <div class="folder-item-wrapper">
<div class="folder-item" :class="{'is-selected': isSelected}" @click="showTree" :style="indent"> <div class="folder-item" :class="{'is-selected': isSelected}" @click="getFolder" :style="indent">
<FontAwesomeIcon class="icon-chevron" :class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}" icon="chevron-right"/> <chevron-right-icon @click.stop="showTree" size="17" class="icon-arrow" :class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}"></chevron-right-icon>
<FontAwesomeIcon class="icon" :icon="directoryIcon"/> <hard-drive-icon v-if="nodes.location === 'base'" size="17" class="icon"></hard-drive-icon>
<folder-icon v-if="nodes.location !== 'base'" size="17" class="icon"></folder-icon>
<span class="label">{{ nodes.name }}</span> <span class="label">{{ nodes.name }}</span>
</div> </div>
@@ -14,6 +15,7 @@
<script> <script>
import TreeMenu from '@/components/Others/TreeMenu' import TreeMenu from '@/components/Others/TreeMenu'
import {FolderIcon, ChevronRightIcon, HardDriveIcon} from 'vue-feather-icons'
import {events} from "@/bus" import {events} from "@/bus"
export default { export default {
@@ -22,20 +24,15 @@
'nodes', 'depth' 'nodes', 'depth'
], ],
components: { components: {
ChevronRightIcon,
HardDriveIcon,
FolderIcon,
TreeMenu, TreeMenu,
}, },
computed: { computed: {
indent() { indent() {
return { paddingLeft: this.depth * 25 + 'px' } return { paddingLeft: this.depth * 20 + 'px' }
}, },
directoryIcon() {
if (this.nodes.location === 'base') {
return 'hdd'
} else {
return 'folder'
}
}
}, },
data() { data() {
return { return {
@@ -44,10 +41,12 @@
} }
}, },
methods: { methods: {
getFolder() {
events.$emit('show-folder-item', this.nodes)
events.$emit('pick-folder', this.nodes)
},
showTree() { showTree() {
this.isVisible = ! this.isVisible this.isVisible = ! this.isVisible
events.$emit('pick-folder', this.nodes)
} }
}, },
created() { created() {
@@ -61,36 +60,46 @@
if (this.nodes.unique_id == node.unique_id) this.isSelected = true if (this.nodes.unique_id == node.unique_id) this.isSelected = true
}) })
// Select clicked folder
events.$on('show-folder-item', node => {
this.isSelected = false
if (this.nodes.unique_id == node.unique_id)
this.isSelected = true
})
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.folder-item { .folder-item {
user-select: none;
display: block; display: block;
padding: 15px 20px; padding: 8px 23px;
@include transition(150ms); @include transition(150ms);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
white-space: nowrap; white-space: nowrap;
border-bottom: 1px solid $light_mode_border;
.icon { .icon {
@include font-size(18); line-height: 0;
width: 15px;
margin-right: 9px; margin-right: 9px;
vertical-align: middle; vertical-align: middle;
margin-top: -1px;
path { path, line, polyline, rect, circle {
fill: $text; @include transition(150ms);
} }
} }
.icon-chevron { .icon-arrow {
@include transition(300ms); @include transition(300ms);
@include font-size(13); margin-right: 4px;
margin-right: 9px;
vertical-align: middle; vertical-align: middle;
opacity: 0; opacity: 0;
@@ -101,14 +110,11 @@
&.is-opened { &.is-opened {
@include transform(rotate(90deg)); @include transform(rotate(90deg));
} }
path {
fill: rgba($text, 0.25);
}
} }
.label { .label {
@include font-size(15); @include transition(150ms);
@include font-size(13);
font-weight: 700; font-weight: 700;
vertical-align: middle; vertical-align: middle;
white-space: nowrap; white-space: nowrap;
@@ -118,16 +124,12 @@
color: $text; color: $text;
} }
&:hover { &:hover,
background: $light_background;
}
&.is-selected { &.is-selected {
background: rgba($theme, .1);
.icon { .icon {
path { path, line, polyline, rect, circle {
fill: $theme; stroke: $theme;
} }
} }
@@ -141,33 +143,18 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.folder-item { .folder-item {
border-bottom: 1px solid $dark_mode_border_color;
.label { .label {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
} }
&:hover { &:hover {
background: $dark_mode_foreground; background: rgba($theme, .1);
} }
&.is-selected { &.is-selected {
background: rgba($theme, .1); background: rgba($theme, .1);
} }
.icon {
path {
fill: lighten($dark_mode_foreground, 10%);
}
}
.icon-chevron {
path {
fill: $theme;
}
}
} }
&.is-selected { &.is-selected {
@@ -0,0 +1,182 @@
<template>
<transition name="folder">
<div class="folder-item-wrapper">
<div class="folder-item" :class="{'is-selected': isSelected}" :style="indent" @click="getFolder">
<chevron-right-icon @click.stop="showTree" size="17" class="icon-arrow"
:class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}"></chevron-right-icon>
<folder-icon size="17" class="icon"></folder-icon>
<span class="label">{{ nodes.name }}</span>
</div>
<TreeMenuNavigator :depth="depth + 1" v-if="isVisible" :nodes="item" v-for="item in nodes.folders"
:key="item.unique_id"/>
</div>
</transition>
</template>
<script>
import TreeMenuNavigator from '@/components/Others/TreeMenuNavigator'
import {FolderIcon, ChevronRightIcon} from 'vue-feather-icons'
import {events} from "@/bus"
export default {
name: 'TreeMenuNavigator',
props: [
'nodes', 'depth'
],
components: {
TreeMenuNavigator,
ChevronRightIcon,
FolderIcon,
},
computed: {
indent() {
let offset = window.innerWidth <= 1024 ? 17 : 22;
let value = this.depth == 0 ? offset : offset + (this.depth * 20);
return {paddingLeft: value + 'px'}
},
},
data() {
return {
isVisible: false,
isSelected: false,
}
},
methods: {
getFolder() {
events.$emit('show-folder', this.nodes)
// Get folder content
this.$store.dispatch('getFolder', [{folder: this.nodes, back: false, init: false}])
},
showTree() {
this.isVisible = !this.isVisible
}
},
created() {
// Select clicked folder
events.$on('show-folder', node => {
this.isSelected = false
if (this.nodes.unique_id == node.unique_id)
this.isSelected = true
})
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.folder-item {
display: block;
padding: 8px 0;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
width: 100%;
.icon {
line-height: 0;
width: 15px;
margin-right: 9px;
vertical-align: middle;
margin-top: -1px;
path, line, polyline, rect, circle {
@include transition(150ms);
}
}
.icon-arrow {
@include transition(300ms);
margin-right: 4px;
vertical-align: middle;
opacity: 0;
&.is-visible {
opacity: 1;
}
&.is-opened {
@include transform(rotate(90deg));
}
}
.label {
@include transition(150ms);
@include font-size(13);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
color: $text;
max-width: 130px;
}
&:hover,
&.is-selected {
.icon {
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.label {
color: $theme;
}
}
}
@media only screen and (max-width: 1024px) {
.folder-item {
padding: 8px 0;
}
}
// Dark mode
@media (prefers-color-scheme: dark) {
.folder-item {
.label {
color: $dark_mode_text_primary;
}
&:hover {
background: rgba($theme, .1);
}
&.is-selected {
background: rgba($theme, .1);
}
}
&.is-selected {
background: rgba($theme, .1);
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.folder-item {
&:hover,
&.is-selected {
background: rgba($theme, .1);
}
}
}
</style>
@@ -0,0 +1,47 @@
<template>
<div class="user-avatar" :class="size">
<img :src="app.user.avatar" :alt="app.user.name">
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: 'UserAvatar',
props: [
'size'
],
computed: {
...mapGetters(['app']),
},
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.user-avatar {
line-height: 0;
img {
border-radius: 6px;
width: 40px;
height: 40px;
}
&.large {
img {
border-radius: 9px;
width: 52px;
height: 52px;
}
}
}
@media (prefers-color-scheme: dark) {
}
</style>
@@ -60,7 +60,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.dropzone { .dropzone {
position: relative; position: relative;
@@ -79,12 +80,10 @@
} }
.image-preview { .image-preview {
width: 75px; width: 56px;
height: 75px; height: 56px;
object-fit: cover; object-fit: cover;
border-radius: 8px; border-radius: 8px;
border: 1px dashed $theme;
padding: 2px;
} }
} }
</style> </style>
+2 -1
View File
@@ -34,7 +34,8 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.vignette { .vignette {
position: absolute; position: absolute;
@@ -0,0 +1,25 @@
<template>
<div class="content-group">
<TextLabel>{{ title }}</TextLabel>
<slot></slot>
</div>
</template>
<script>
import TextLabel from '@/components/Others/TextLabel'
export default {
name: 'ContentGroup',
props: ['title'],
components: {
TextLabel,
}
}
</script>
<style scoped lang="scss">
.content-group {
margin-bottom: 30px;
}
</style>
@@ -0,0 +1,43 @@
<template>
<section class="content-sidebar">
<slot></slot>
</section>
</template>
<script>
export default {
name: 'ContentSidebar',
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.content-sidebar {
background: linear-gradient(0deg, rgba(246, 245, 241, 0.4) 0%, rgba(243, 244, 246, 0.4) 100%);
user-select: none;
padding-top: 25px;
overflow-y: auto;
flex: 0 0 225px;
}
@media only screen and (max-width: 1024px) {
.content-sidebar {
flex: 0 0 205px;
}
}
@media only screen and (max-width: 690px) {
.content-sidebar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.content-sidebar {
background: rgba($dark_mode_foreground, 0.2);
}
}
</style>
@@ -1,183 +0,0 @@
<template>
<div class="file-item">
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" />
<!--Image thumbnail-->
<img v-if="isImage" class="image" :src="file.thumbnail" :alt="file.name" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" />
</div>
<!--Name-->
<div class="item-name">
<!--Name-->
<span class="name" >{{ file.name }}</span>
<!--Other attributes-->
<span v-if="! isFolder" class="item-size">{{ file.filesize }}</span>
<span v-if="isFolder" class="item-length">{{ file.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ file.created_at }}</span>
</div>
</div>
</template>
<script>
export default {
name: 'FileListItemThumbnail',
props: ['file'],
computed: {
isFolder() {
return this.file.type === 'folder'
},
isFile() {
return this.file.type !== 'folder' && this.file.type !== 'image'
},
isImage() {
return this.file.type === 'image'
}
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.file-item {
display: flex;
align-items: center;
padding: 10px 15px;
@include transition(150ms);
cursor: pointer;
&:hover {
background: rgba($theme, .1);
.item-name .name {
color: $theme;
}
}
.item-name {
display: block;
margin-left: 10px;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.item-size,
.item-length {
@include font-size(11);
font-weight: 400;
color: $text-muted;
display: block;
}
.name {
white-space: nowrap;
}
.name {
color: $text;
@include font-size(14);
font-weight: 700;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.icon-item {
position: relative;
min-width: 40px;
text-align: center;
line-height: 0;
.file-icon {
@include font-size(35);
path {
fill: #fafafc;
stroke: #dfe0e8;
stroke-width: 1;
}
}
.file-icon-text {
top: 40%;
@include font-size(9);
line-height: 1;
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
}
}
@media (prefers-color-scheme: dark) {
.file-item {
.icon-item .file-icon {
path {
fill: $dark_mode_background;
stroke: #2F3C54;
}
}
&:hover,
&.is-clicked {
background: rgba($theme, .1);
}
.item-name .name {
color: $dark_mode_text_primary;
}
}
}
@media (max-width: 690px) and (prefers-color-scheme: dark){
.file-item {
.icon-item .file-icon {
path {
fill: $dark_mode_foreground;
}
}
}
}
</style>
+216
View File
@@ -0,0 +1,216 @@
<template>
<nav class="menu-bar" v-if="app">
<!--Navigation Icons-->
<div class="icon-navigation menu">
<router-link :to="{name: 'Profile'}" class="icon-navigation-item user">
<UserAvatar />
</router-link>
<router-link :to="{name: 'Files'}" :title="$t('locations.home')" class="icon-navigation-item home">
<div class="button-icon">
<hard-drive-icon size="19"></hard-drive-icon>
</div>
</router-link>
<router-link :to="{name: 'SharedFiles'}" :title="$t('locations.shared')" class="icon-navigation-item shared">
<div class="button-icon">
<share-icon size="19"></share-icon>
</div>
</router-link>
<router-link :to="{name: 'Trash'}" :title="$t('locations.trash')" class="icon-navigation-item trash">
<div class="button-icon">
<trash-2-icon size="19"></trash-2-icon>
</div>
</router-link>
<router-link :to="{name: 'Profile'}" :class="{'is-active': $isThisRoute($route, ['Password'])}" class="icon-navigation-item settings">
<div class="button-icon">
<settings-icon size="19"></settings-icon>
</div>
</router-link>
</div>
<!--User avatar & Logout-->
<ul class="icon-navigation logout">
<li @click="$store.dispatch('logOut')" class="icon-navigation-item">
<div class="button-icon">
<power-icon size="19"></power-icon>
</div>
</li>
</ul>
</nav>
</template>
<script>
import UserAvatar from '@/components/Others/UserAvatar'
import {mapGetters} from 'vuex'
import {
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
HardDriveIcon,
SettingsIcon,
UserAvatar,
Trash2Icon,
PowerIcon,
ShareIcon,
},
computed: {
...mapGetters(['app']),
},
mounted() {
this.$store.dispatch('getAppData')
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.menu-bar {
background: linear-gradient(180deg, rgba(246, 245, 241, 0.8) 0%, rgba(243, 244, 246, 0.8) 100%);
user-select: none;
padding-top: 25px;
display: grid;
width: 72px;
}
.icon-navigation {
text-align: center;
&.menu {
margin-bottom: auto;
}
&.logout {
margin-top: auto;
}
.icon-navigation-item {
display: block;
margin-bottom: 10px;
&.user {
margin-bottom: 20px;
display: block;
}
}
.button-icon {
cursor: pointer;
border-radius: 4px;
padding: 12px;
display: inline-block;
line-height: 0;
@include transition(150ms);
&:hover {
background: darken($light_background, 5%);
}
path, line, polyline, rect, circle {
@include transition(150ms);
stroke: black;
}
}
.router-link-active,
.is-active {
&.home {
.button-icon {
background: rgba($theme, 0.1);
path, line, polyline, rect, circle {
stroke: $theme;
}
}
}
&.shared {
.button-icon {
background: rgba($yellow, 0.1);
path, line, polyline, rect, circle {
stroke: $yellow;
}
}
}
&.trash {
.button-icon {
background: rgba($red, 0.1);
path, line, polyline, rect, circle {
stroke: $red;
}
}
}
&.settings {
.button-icon {
background: rgba($purple, 0.1);
path, line, polyline, rect, circle {
stroke: $purple;
}
}
}
}
}
@media only screen and (max-width: 1024px) {
.menu-bar {
width: 60px;
}
.icon-navigation {
.icon-navigation-item {
margin-bottom: 15px;
}
.button-icon {
padding: 8px;
}
}
}
@media only screen and (max-width: 690px) {
.menu-bar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.icon-navigation {
.button-icon {
&:hover {
background: $dark_mode_background;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
.menu-bar {
background: $dark_mode_foreground;
}
}
</style>
-460
View File
@@ -1,460 +0,0 @@
<template>
<transition name="sidebar">
<div id="sidebar" v-if="app && (isVisibleSidebar || ! isSmallAppSize)">
<!--User Headline-->
<UserHeadline/>
<!--Content-->
<div class="content-scroller">
<!--Locations-->
<div class="menu-list-wrapper">
<TextLabel>{{ $t('sidebar.locations') }}</TextLabel>
<ul class="menu-list">
<li class="menu-list-item" :class="{'is-active': isBaseLocation}" @click="goHome">
<FontAwesomeIcon class="icon" icon="hdd"/>
<span class="label">{{ $t('locations.home') }}</span>
</li>
<li class="menu-list-item" :class="{'is-active': isSharedLocation}" @click="getShared">
<FontAwesomeIcon class="icon" icon="share"/>
<span class="label">{{ $t('locations.shared') }}</span>
</li>
<li class="menu-list-item" :class="{'is-active': isTrashLocation}" @click="getTrash">
<FontAwesomeIcon class="icon" icon="trash-alt"/>
<span class="label">{{ $t('locations.trash') }}</span>
</li>
</ul>
</div>
<!--Favourites-->
<div class="menu-list-wrapper favourites"
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish($event)"
:class="{ 'is-dragenter': area }"
>
<TextLabel>{{ $t('sidebar.favourites') }}</TextLabel>
<transition-group tag="ul" class="menu-list" name="folder-item">
<li class="empty-list" v-if="app.favourites.length == 0" :key="0">
{{ $t('sidebar.favourites_empty') }}
</li>
<li @click.stop="openFolder(folder)" class="menu-list-item" v-for="folder in app.favourites"
:key="folder.unique_id">
<div>
<FontAwesomeIcon class="icon" icon="folder"/>
<span class="label">{{ folder.name }}</span>
</div>
<FontAwesomeIcon @click.stop="removeFavourite(folder)" v-if="! $isMobile()" class="delete-icon" icon="times"/>
</li>
</transition-group>
</div>
<!--Last Uploads-->
<div class="menu-list-wrapper">
<TextLabel>{{ $t('sidebar.latest') }}</TextLabel>
<p class="empty-list" v-if="app.latest_uploads.length == 0">{{ $t('sidebar.latest_empty') }}</p>
<FileListItemThumbnail @dblclick.native="downloadFile(item)" @click.native="showFileDetail(item)" :file="item" v-for="item in app.latest_uploads" :key="item.unique_id"/>
</div>
</div>
<!--Storage Size Info-->
<StorageSize v-if="config.storageLimit"/>
<!--Mobile logout button-->
<div v-if="isSmallAppSize" class="log-out-button">
<ButtonBase @click.native="$store.dispatch('logOut')" button-style="danger">{{ $t('context_menu.log_out') }}</ButtonBase>
</div>
</div>
</transition>
</template>
<script>
import FileListItemThumbnail from '@/components/Sidebar/FileListItemThumbnail'
import UserHeadline from '@/components/Sidebar/UserHeadline'
import ButtonBase from '@/components/FilesView/ButtonBase'
import StorageSize from '@/components/Sidebar/StorageSize'
import TextLabel from '@/components/Others/TextLabel'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'Sidebar',
components: {
FileListItemThumbnail,
UserHeadline,
StorageSize,
ButtonBase,
TextLabel,
},
computed: {
...mapGetters(['homeDirectory', 'app', 'appSize', 'config', 'currentFolder']),
isTrashLocation() {
return this.currentFolder && this.currentFolder.location === 'trash-root' || this.currentFolder && this.currentFolder.location === 'trash'
},
isBaseLocation() {
return this.currentFolder && this.currentFolder.location === 'base'
},
isSharedLocation() {
return this.currentFolder && this.currentFolder.location === 'shared'
},
isSmallAppSize() {
return this.appSize === 'small'
}
},
data() {
return {
area: false,
draggedItem: undefined,
isVisibleSidebar: false,
}
},
methods: {
getShared() {
this.$store.dispatch('getShared')
},
getTrash() {
this.$store.dispatch('getTrash')
},
goHome() {
this.$store.commit('FLUSH_BROWSER_HISTORY')
this.$store.dispatch('getFolder', [this.homeDirectory, true])
},
openFolder(folder) {
// Go to folder
this.$store.dispatch('getFolder', [folder, false])
},
downloadFile(file) {
this.$downloadFile(file.file_url, file.name + '.' + file.mimetype)
},
showFileDetail(file) {
// Dispatch load file info detail
this.$store.dispatch('getFileDetail', file)
// Show panel if is not open
this.$store.dispatch('fileInfoToggle', true)
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
this.area = true
},
dragLeave() {
this.area = false
},
dragFinish() {
this.area = false
// Check if draged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if folder exist in favourites
if (this.app.favourites.find(folder => folder.unique_id == this.draggedItem.unique_id)) return
// Store favourites folder
this.$store.dispatch('addToFavourites', this.draggedItem)
},
removeFavourite(folder) {
// Remove favourites folder
this.$store.dispatch('removeFromFavourites', folder)
}
},
mounted() {
this.$store.dispatch('getAppData')
// Listen for dragstart folder items
events.$on('dragstart', (item) => this.draggedItem = item)
// Listen for show sidebar
events.$on('show:sidebar', () => {
this.isVisibleSidebar = !this.isVisibleSidebar
})
// Listen for hide sidebar
events.$on('show:content', () => {
if (this.isVisibleSidebar) this.isVisibleSidebar = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
#sidebar {
position: relative;
flex: 0 0 265px;
background: $light_background;
}
.content-scroller {
bottom: 50px;
position: absolute;
top: 75px;
left: 0;
right: 0;
overflow-x: auto;
.menu-list-wrapper:first-of-type {
margin-top: 20px;
}
.text-label {
margin-left: 15px;
}
}
.menu-list-wrapper {
margin-bottom: 20px;
.menu-list {
.menu-list-item {
display: block;
padding: 8px 15px 8px 25px;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
.icon {
@include font-size(13);
width: 15px;
margin-right: 9px;
vertical-align: middle;
path {
fill: $text;
}
}
.delete-icon {
display: none;
position: absolute;
right: 15px;
top: 50%;
@include transform(translateY(-50%));
@include font-size(12);
path {
fill: $text-muted;
}
}
.label {
@include font-size(14);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
&:hover,
&.is-active {
background: rgba($theme, .1);
.icon {
path {
fill: $theme;
}
}
.label {
color: $theme;
}
.delete-icon {
display: block;
}
}
}
}
&.favourites {
&.is-dragenter {
.menu-list {
border: 2px dashed $theme;
border-radius: 8px;
}
}
.menu-list {
border: 2px dashed transparent;
.menu-list-item {
padding: 8px 23px;
.icon {
margin-right: 5px;
@include font-size(14);
width: 20px;
path {
fill: $theme;
}
}
&:hover {
background: rgba($theme, .1);
.icon {
path {
fill: $theme;
}
}
}
}
}
}
.empty-list {
@include font-size(12);
color: $text-muted;
display: block;
padding: 15px;
}
}
.log-out-button {
margin-top: 15px;
padding: 15px;
button {
width: 100%;
}
}
@media only screen and (max-width: 1024px) {
#sidebar {
flex: 0 0 265px;
}
.menu-list-wrapper .menu-list .menu-list-item .label {
max-width: 190px;
}
}
@media only screen and (max-width: 690px) {
.content-scroller {
bottom: initial;
position: relative;
top: initial;
overflow-x: initial;
}
#sidebar {
position: absolute;
overflow-y: auto;
top: 0;
left: 0;
right: 0;
z-index: 99;
bottom: 0;
background: white;
opacity: 1;
}
.menu-list-wrapper {
&.favourites {
.menu-list .menu-list-item {
padding: 10px 26px;
}
}
.menu-list .menu-list-item {
padding: 10px 26px;
.label {
@include font-size(14);
}
}
}
.log-out-button {
margin-top: 0;
}
}
@media (prefers-color-scheme: dark) {
#sidebar {
background: $dark_mode_background;
}
.menu-list-wrapper {
.menu-list .menu-list-item {
.label {
color: $dark_mode_text_primary;
}
.icon {
path {
fill: lighten($dark_mode_foreground, 10%);
}
}
&:hover {
background: rgba($theme, .1);
}
}
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
#sidebar {
background: $dark_mode_background;
}
}
.sidebar-enter-active {
transition: all 300ms ease;
}
.sidebar-enter /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(-100%);
}
// Transition
.folder-item-move {
transition: transform 300s ease;
}
.folder-item-enter-active {
transition: all 300ms ease;
}
.folder-item-leave-active {
transition: all 300ms;
}
.folder-item-enter, .folder-item-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(30px);
}
.folder-item-leave-active {
position: absolute;
}
</style>
@@ -24,7 +24,8 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.storage-size { .storage-size {
position: absolute; position: absolute;
+11 -192
View File
@@ -1,26 +1,7 @@
<template> <template>
<div class="user-headline-wrapper" v-if="app"> <div class="user-meta" v-if="app">
<div class="user-headline" @click="openMenu"> <b class="name">{{ app.user.name }}</b>
<div class="user-avatar"> <span class="email">{{ app.user.email }}</span>
<img :src="app.user.avatar" :alt="app.user.name">
</div>
<div class="user-name">
<b class="name">{{ app.user.name }}</b>
<span class="email">{{ app.user.email }}</span>
</div>
</div>
<transition name="user-menu">
<div class="user-menu" v-if="isOpenedMenu">
<ul class="menu-options" @click="closeMenu">
<li class="menu-option">
<router-link :to="{name: 'Profile'}">
{{ $t('context_menu.profile_settings') }}
</router-link>
</li>
<li class="menu-option" @click="$store.dispatch('logOut')">{{ $t('context_menu.log_out') }}</li>
</ul>
</div>
</transition>
</div> </div>
</template> </template>
@@ -31,198 +12,36 @@
export default { export default {
name: 'UserHeadline', name: 'UserHeadline',
computed: { computed: {
...mapGetters(['app', 'appSize']), ...mapGetters(['app']),
isSmallAppSize() {
return this.appSize === 'small'
}
},
data() {
return {
isOpenedMenu: false,
}
},
methods: {
openMenu() {
// If is mobile, then go to user settings page, else, open menu
if ( this.isSmallAppSize ) {
events.$emit('show:sidebar')
this.$router.push({name: 'Profile'})
} else {
this.isOpenedMenu = !this.isOpenedMenu
}
},
closeMenu() {
this.isOpenedMenu = !this.isOpenedMenu
},
}, },
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.user-headline-wrapper { .user-meta {
position: relative; padding-left: 20px;
}
.user-headline {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 15px;
padding: 5px;
user-select: none;
border-radius: 8px;
display: flex;
align-items: center;
cursor: pointer;
@include transition(150ms);
background: darken($light_background, 3%);
&:active {
transform: scale(0.95);
}
}
.user-name {
.name, .email {
white-space: nowrap;
width: 180px;
overflow: hidden;
text-overflow: ellipsis;
}
.name { .name {
display: block; display: block;
@include font-size(17); @include font-size(18);
line-height: 1;
color: $text;
} }
.email { .email {
@include font-size(13);
color: $theme;
display: block; display: block;
margin-top: 2px; @include font-size(12);
color: $theme;
font-weight: 600; font-weight: 600;
} }
} }
.user-avatar {
line-height: 0;
margin-right: 15px;
img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 8px;
}
}
.user-menu {
position: absolute;
top: 75px;
left: 0;
right: 0;
margin: 15px;
z-index: 9;
}
.menu-options {
list-style: none;
width: 100%;
margin: 0;
padding: 0;
box-shadow: $shadow;
background: white;
border-radius: 8px;
.menu-option {
font-weight: 700;
@include font-size(15);
padding: 15px 30px;
cursor: pointer;
width: 100%;
color: $text;
a {
text-decoration: none;
color: $text;
}
&:hover {
background: $light_background;
color: $theme;
}
}
}
@media only screen and (max-width: 690px) { @media only screen and (max-width: 690px) {
.user-headline {
position: relative;
margin-bottom: 40px;
background: transparent;
padding: 0;
}
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.user-headline {
background: $dark_mode_background;
padding: 0;
&:hover {
background: transparent;
}
}
.user-name {
.name {
color: $dark_mode_text_primary;
}
}
.menu-options {
background: $dark_mode_background;
.menu-option {
color: $dark_mode_text_primary;
a {
color: $dark_mode_text_primary;
}
&:hover {
background: $dark_mode_foreground;
}
}
}
} }
// Transition
.user-menu-enter-active {
transition: all 150ms ease;
}
.user-menu-leave-active {
transition: all 150ms ease;
}
.user-menu-enter,
.user-menu-leave-to {
opacity: 0;
transform: translateY(-35%);
}
</style> </style>
+10
View File
@@ -2,6 +2,7 @@ import store from './store/index'
import {debounce, includes} from "lodash"; import {debounce, includes} from "lodash";
import {events} from './bus' import {events} from './bus'
import axios from 'axios' import axios from 'axios'
import router from '@/router'
const Helpers = { const Helpers = {
install(Vue) { install(Vue) {
@@ -88,6 +89,10 @@ const Helpers = {
// Append form data // Append form data
formData.append('parent_id', rootFolder) formData.append('parent_id', rootFolder)
console.log(i);
console.log(files[i]);
console.log(formData);
// Upload data // Upload data
await store.dispatch('uploadFiles', formData) await store.dispatch('uploadFiles', formData)
.then(() => { .then(() => {
@@ -233,6 +238,11 @@ const Helpers = {
events.$emit('popup:close') events.$emit('popup:close')
} }
Vue.prototype.$isThisRoute = function(route, locations) {
return includes(locations, route.name)
}
Vue.prototype.$isThisLocation = function(location) { Vue.prototype.$isThisLocation = function(location) {
// Get current location // Get current location
+5 -5
View File
@@ -7,7 +7,7 @@
"store_pass": "Store New Password", "store_pass": "Store New Password",
"change_pass": "Change Password", "change_pass": "Change Password",
"profile_info": "Profile Information", "profile_info": "Profile Information",
"photo_description": "Change Your Profile Picture", "photo_description": "Change your avatar",
"photo_supported": "Supported formats are .png, .jpg, .jpeg." "photo_supported": "Supported formats are .png, .jpg, .jpeg."
}, },
"page_registration": { "page_registration": {
@@ -18,7 +18,7 @@
"label_name": "Full Name:", "label_name": "Full Name:",
"placeholder_name": "Type your full name", "placeholder_name": "Type your full name",
"label_pass": "Create password:", "label_pass": "Create password:",
"placeholder_pass": "Your new password", "placeholder_pass": "New password",
"label_confirm_pass": "Confirm password:", "label_confirm_pass": "Confirm password:",
"placeholder_confirm_pass": "Confirm your new password", "placeholder_confirm_pass": "Confirm your new password",
"button_create_account": "Create Account", "button_create_account": "Create Account",
@@ -40,8 +40,8 @@
"subtitle": "Create your new password here:", "subtitle": "Create your new password here:",
"button_update": "Update Password", "button_update": "Update Password",
"label_email": "Email:", "label_email": "Email:",
"label_new_pass": "Your new password", "label_new_pass": "New password",
"label_confirm_pass": "Confirm new password" "label_confirm_pass": "Confirm password"
}, },
"page_forgotten_password": { "page_forgotten_password": {
"title": "Forgotten Password?", "title": "Forgotten Password?",
@@ -75,7 +75,7 @@
"progress": "Uploading files {current}/{total}" "progress": "Uploading files {current}/{total}"
}, },
"inputs": { "inputs": {
"placeholder_search_files": "Search files" "placeholder_search_files": "Search files or folders..."
}, },
"messages": { "messages": {
"nothing_to_preview": "There is nothing to preview.", "nothing_to_preview": "There is nothing to preview.",
+56 -5
View File
@@ -8,8 +8,14 @@ import NotFoundShared from './views/Shared/NotFoundShared'
import ForgottenPassword from './views/Auth/ForgottenPassword' import ForgottenPassword from './views/Auth/ForgottenPassword'
import CreateNewPassword from './views/Auth/CreateNewPassword' import CreateNewPassword from './views/Auth/CreateNewPassword'
import Files from './views/Files' import Settings from './views/Settings'
import Profile from './views/Profile' import Profile from './views/User/Profile'
import Trash from './views/FilePages/Trash'
import Files from './views/FilePages/Files'
import Password from './views/User/Password'
import SharedFiles from './views/FilePages/SharedFiles'
import MobileSettings from './views/Mobile/MobileSettings'
Vue.use(Router) Vue.use(Router)
@@ -73,13 +79,58 @@ const router = new Router({
}, },
}, },
{ {
name: 'Profile', name: 'SharedFiles',
path: '/profile', path: '/shared-files',
component: Profile, component: SharedFiles,
meta: { meta: {
requiresAuth: true requiresAuth: true
}, },
}, },
{
name: 'Trash',
path: '/trash',
component: Trash,
meta: {
requiresAuth: true
},
},
{
name: 'Settings',
path: '/settings',
component: Settings,
meta: {
requiresAuth: true
},
children: [
{
name: 'Profile',
path: 'profile',
component: Profile,
meta: {
requiresAuth: true,
title: 'User Profile'
},
},
{
name: 'Password',
path: '/settings/password',
component: Password,
meta: {
requiresAuth: true,
title: 'Change Password'
},
},
]
},
{
name: 'MobileSettings',
path: '/settings-mobile',
component: MobileSettings,
meta: {
requiresAuth: true,
title: 'Settings'
},
}
], ],
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
+11 -16
View File
@@ -3,6 +3,8 @@ const defaultState = {
FilePreviewType: localStorage.getItem('preview_type') || 'list', FilePreviewType: localStorage.getItem('preview_type') || 'list',
appSize: undefined, appSize: undefined,
config: undefined, config: undefined,
authorized: undefined,
homeDirectory: undefined,
} }
const actions = { const actions = {
changePreviewType: ({commit, dispatch, state, getters}) => { changePreviewType: ({commit, dispatch, state, getters}) => {
@@ -14,18 +16,6 @@ const actions = {
// Change preview // Change preview
commit('CHANGE_PREVIEW', previewType) commit('CHANGE_PREVIEW', previewType)
if (getters.currentFolder.location === 'trash-root') {
dispatch('getTrash')
} else {
if ( getters.currentFolder.location === 'public' ) {
dispatch('browseShared', [getters.currentFolder, false, true])
} else {
dispatch('getFolder', [getters.currentFolder, false, true])
}
}
}, },
fileInfoToggle: (context, visibility = undefined) => { fileInfoToggle: (context, visibility = undefined) => {
if (!visibility) { if (!visibility) {
@@ -40,6 +30,11 @@ const actions = {
}, },
} }
const mutations = { const mutations = {
INIT(state, data) {
state.config = data.config
state.authorized = data.authCookie
state.homeDirectory = data.rootDirectory
},
FILE_INFO_TOGGLE(state, isVisible) { FILE_INFO_TOGGLE(state, isVisible) {
state.fileInfoPanelVisible = isVisible state.fileInfoPanelVisible = isVisible
@@ -48,13 +43,12 @@ const mutations = {
SET_APP_WIDTH(state, scale) { SET_APP_WIDTH(state, scale) {
state.appSize = scale state.appSize = scale
}, },
SET_AUTHORIZED(state, data) {
state.authorized = data
},
CHANGE_PREVIEW(state, type) { CHANGE_PREVIEW(state, type) {
state.FilePreviewType = type state.FilePreviewType = type
}, },
SET_CONFIG(state, config) {
state.config = config
},
} }
const getters = { const getters = {
fileInfoVisible: state => state.fileInfoPanelVisible, fileInfoVisible: state => state.fileInfoPanelVisible,
@@ -62,6 +56,7 @@ const getters = {
appSize: state => state.appSize, appSize: state => state.appSize,
api: state => state.config.api, api: state => state.config.api,
config: state => state.config, config: state => state.config,
homeDirectory: state => state.homeDirectory,
} }
export default { export default {
+99 -129
View File
@@ -1,16 +1,13 @@
import axios from 'axios' import axios from 'axios'
import {events} from '@/bus' import {events} from '@/bus'
import router from '@/router' import router from '@/router'
import {includes} from 'lodash'
import i18n from '@/i18n/index' import i18n from '@/i18n/index'
const defaultState = { const defaultState = {
uploadingFilesCount: undefined, uploadingFilesCount: undefined,
fileInfoDetail: undefined, fileInfoDetail: undefined,
currentFolder: undefined, currentFolder: undefined,
homeDirectory: undefined,
uploadingFileProgress: 0, uploadingFileProgress: 0,
filesViewWidth: undefined,
navigation: undefined, navigation: undefined,
isSearching: false, isSearching: false,
browseHistory: [], browseHistory: [],
@@ -19,56 +16,45 @@ const defaultState = {
} }
const actions = { const actions = {
getFolder: (context, [folder, back = false, init = false]) => { getFolder: ({commit, getters}, [payload]) => {
events.$emit('show:content') commit('LOADING_STATE', {loading: true, data: []})
// Go to files view if (payload.init)
if (!includes(['Files', 'SharedPage'], router.currentRoute.name)) { commit('FLUSH_FOLDER_HISTORY')
router.push({name: 'Files'})
}
context.commit('LOADING_STATE', true)
context.commit('FLUSH_DATA')
// Clear search // Clear search
if (context.state.isSearching) { if (getters.isSearching) {
context.commit('CHANGE_SEARCHING_STATE', false) commit('CHANGE_SEARCHING_STATE', false)
events.$emit('clear-query') events.$emit('clear-query')
} }
// Create current folder for history // Set folder location
let currentFolder = { payload.folder.location = payload.folder.deleted_at || payload.folder.location === 'trash' ? 'trash' : 'base'
name: folder.name,
unique_id: folder.unique_id,
location: folder.deleted_at || folder.location === 'trash' ? 'trash' : 'base'
}
let url = currentFolder.location === 'trash' if (! payload.back)
? '/folders/' + currentFolder.unique_id + '?trash=true' commit('STORE_PREVIOUS_FOLDER', getters.currentFolder)
: '/folders/' + currentFolder.unique_id
let url = payload.folder.location === 'trash'
? '/folders/' + payload.folder.unique_id + '?trash=true'
: '/folders/' + payload.folder.unique_id
axios axios
.get(context.getters.api + url) .get(getters.api + url)
.then(response => { .then(response => {
context.commit('LOADING_STATE', false) commit('LOADING_STATE', {loading: false, data: response.data})
context.commit('GET_DATA', response.data) commit('STORE_CURRENT_FOLDER', payload.folder)
context.commit('STORE_CURRENT_FOLDER', currentFolder)
if (payload.back)
commit('REMOVE_BROWSER_HISTORY')
events.$emit('scrollTop') events.$emit('scrollTop')
if (back) {
context.commit('REMOVE_BROWSER_HISTORY')
} else {
if (!init) context.commit('ADD_BROWSER_HISTORY', currentFolder)
}
}) })
.catch(error => { .catch(error => {
// Redirect if unauthenticated // Redirect if unauthenticated
if ([401, 403].includes(error.response.status)) { if ([401, 403].includes(error.response.status)) {
context.commit('SET_AUTHORIZED', false) commit('SET_AUTHORIZED', false)
router.push({name: 'SignIn'}) router.push({name: 'SignIn'})
} else { } else {
@@ -81,83 +67,89 @@ const actions = {
} }
}) })
}, },
getShared: (context, back = false) => { getLatest: ({commit, getters}) => {
events.$emit('show:content') commit('LOADING_STATE', {loading: true, data: []})
// Go to files view commit('STORE_PREVIOUS_FOLDER', getters.currentFolder)
if (router.currentRoute.name !== 'Files') { commit('STORE_CURRENT_FOLDER', {
router.push({name: 'Files'}) name: 'Latest',
}
if (!back) context.commit('FLUSH_BROWSER_HISTORY')
context.commit('FLUSH_DATA')
context.commit('LOADING_STATE', true)
// Create shared object for history
let trash = {
name: 'Shared',
unique_id: undefined, unique_id: undefined,
location: 'shared', location: 'latest',
} })
axios axios
.get(context.getters.api + '/shared-all') .get(getters.api + '/latest')
.then(response => { .then(response => {
context.commit('GET_DATA', response.data) commit('LOADING_STATE', {loading: false, data: response.data})
context.commit('LOADING_STATE', false) events.$emit('scrollTop')
context.commit('STORE_CURRENT_FOLDER', trash) })
context.commit('ADD_BROWSER_HISTORY', trash) .catch(() => isSomethingWrong())
},
getShared: ({commit, getters}) => {
commit('LOADING_STATE', {loading: true, data: []})
commit('FLUSH_FOLDER_HISTORY')
let currentFolder = {
name: 'Shared',
location: 'shared',
unique_id: undefined,
}
commit('STORE_CURRENT_FOLDER', currentFolder)
axios
.get(getters.api + '/shared-all')
.then(response => {
commit('LOADING_STATE', {loading: false, data: response.data})
commit('STORE_PREVIOUS_FOLDER', currentFolder)
events.$emit('scrollTop') events.$emit('scrollTop')
}) })
.catch(() => { .catch(() => isSomethingWrong())
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
})
}, },
getTrash: (context, back = false) => { getParticipantUploads: ({commit, getters}) => {
events.$emit('show:content') commit('LOADING_STATE', {loading: true, data: []})
// Go to files view commit('STORE_PREVIOUS_FOLDER', getters.currentFolder)
if (router.currentRoute.name !== 'Files') { commit('STORE_CURRENT_FOLDER', {
router.push({name: 'Files'}) name: 'Participant Uploads',
} unique_id: undefined,
location: 'participant_uploads',
})
if (!back) context.commit('FLUSH_BROWSER_HISTORY') axios
context.commit('FLUSH_DATA') .get(getters.api + '/participant-uploads')
context.commit('LOADING_STATE', true) .then(response => {
commit('LOADING_STATE', {loading: false, data: response.data})
events.$emit('scrollTop')
})
.catch(() => isSomethingWrong())
},
getTrash: ({commit, getters}) => {
commit('LOADING_STATE', {loading: true, data: []})
commit('FLUSH_FOLDER_HISTORY')
// Create trash object for history
let trash = { let trash = {
name: 'Trash', name: 'Trash',
unique_id: undefined, unique_id: undefined,
location: 'trash-root', location: 'trash-root',
} }
commit('STORE_CURRENT_FOLDER', trash)
axios axios
.get(context.getters.api + '/trash') .get(getters.api + '/trash')
.then(response => { .then(response => {
context.commit('GET_DATA', response.data) commit('LOADING_STATE', {loading: false, data: response.data})
context.commit('LOADING_STATE', false) commit('STORE_PREVIOUS_FOLDER', trash)
context.commit('STORE_CURRENT_FOLDER', trash)
context.commit('ADD_BROWSER_HISTORY', trash)
events.$emit('scrollTop') events.$emit('scrollTop')
}) })
.catch(() => { .catch(() => isSomethingWrong())
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
})
}, },
getSearchResult: ({commit, getters}, query) => { getSearchResult: ({commit, getters}, query) => {
commit('FLUSH_DATA') commit('LOADING_STATE', {loading: true, data: []})
commit('LOADING_STATE', true)
commit('CHANGE_SEARCHING_STATE', true) commit('CHANGE_SEARCHING_STATE', true)
// Get route // Get route
@@ -175,30 +167,17 @@ const actions = {
params: {query: query} params: {query: query}
}) })
.then(response => { .then(response => {
commit('LOADING_STATE', false) commit('LOADING_STATE', {loading: false, data: response.data})
commit('GET_DATA', response.data)
})
.catch(() => {
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}) })
.catch(() => isSomethingWrong())
}, },
getFileDetail: (context, file) => { getFileDetail: ({commit, getters}, file) => {
axios axios
.get(context.getters.api + '/file-detail/' + file.unique_id) .get(getters.api + '/file-detail/' + file.unique_id)
.then(response => { .then(response => {
context.commit('LOAD_FILEINFO_DETAIL', response.data) commit('LOAD_FILEINFO_DETAIL', response.data)
})
.catch(() => {
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}) })
.catch(() => isSomethingWrong())
}, },
getFolderTree: ({commit, getters}) => { getFolderTree: ({commit, getters}) => {
@@ -224,11 +203,7 @@ const actions = {
.catch((error) => { .catch((error) => {
reject(error) reject(error)
// Show error message isSomethingWrong()
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}) })
}) })
}, },
@@ -238,13 +213,11 @@ const mutations = {
UPDATE_FOLDER_TREE(state, tree) { UPDATE_FOLDER_TREE(state, tree) {
state.navigation = tree state.navigation = tree
}, },
LOADING_STATE(state, val) { LOADING_STATE(state, payload) {
state.isLoading = val state.data = payload.data
state.isLoading = payload.loading
}, },
SET_START_DIRECTORY(state, directory) { FLUSH_FOLDER_HISTORY(state) {
state.homeDirectory = directory
},
FLUSH_BROWSER_HISTORY(state) {
state.browseHistory = [] state.browseHistory = []
}, },
FLUSH_SHARED(state, unique_id) { FLUSH_SHARED(state, unique_id) {
@@ -252,7 +225,7 @@ const mutations = {
if (item.unique_id == unique_id) item.shared = undefined if (item.unique_id == unique_id) item.shared = undefined
}) })
}, },
ADD_BROWSER_HISTORY(state, folder) { STORE_PREVIOUS_FOLDER(state, folder) {
state.browseHistory.push(folder) state.browseHistory.push(folder)
}, },
REMOVE_BROWSER_HISTORY(state) { REMOVE_BROWSER_HISTORY(state) {
@@ -295,12 +268,6 @@ const mutations = {
if (item.unique_id == data.item_id) item.shared = data if (item.unique_id == data.item_id) item.shared = data
}) })
}, },
FLUSH_DATA(state) {
state.data = []
},
GET_DATA(state, loaded_data) {
state.data = loaded_data
},
ADD_NEW_FOLDER(state, folder) { ADD_NEW_FOLDER(state, folder) {
state.data.unshift(folder) state.data.unshift(folder)
}, },
@@ -318,17 +285,12 @@ const mutations = {
STORE_CURRENT_FOLDER(state, folder) { STORE_CURRENT_FOLDER(state, folder) {
state.currentFolder = folder state.currentFolder = folder
}, },
SET_FILES_VIEW_SIZE(state, type) {
state.filesViewWidth = type
},
} }
const getters = { const getters = {
uploadingFileProgress: state => state.uploadingFileProgress, uploadingFileProgress: state => state.uploadingFileProgress,
uploadingFilesCount: state => state.uploadingFilesCount, uploadingFilesCount: state => state.uploadingFilesCount,
fileInfoDetail: state => state.fileInfoDetail, fileInfoDetail: state => state.fileInfoDetail,
filesViewWidth: state => state.filesViewWidth,
homeDirectory: state => state.homeDirectory,
currentFolder: state => state.currentFolder, currentFolder: state => state.currentFolder,
browseHistory: state => state.browseHistory, browseHistory: state => state.browseHistory,
isSearching: state => state.isSearching, isSearching: state => state.isSearching,
@@ -337,6 +299,14 @@ const getters = {
data: state => state.data, data: state => state.data,
} }
// Show error message
function isSomethingWrong() {
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}
export default { export default {
state: defaultState, state: defaultState,
getters, getters,
+31 -12
View File
@@ -1,10 +1,11 @@
import i18n from '@/i18n/index' import i18n from '@/i18n/index'
import router from '@/router' import router from '@/router'
import {events} from '@/bus' import {events} from '@/bus'
import {last} from 'lodash'
import axios from 'axios' import axios from 'axios'
const actions = { const actions = {
moveItem: ({commit, getters}, [item_from, to_item]) => { moveItem: ({commit, getters, dispatch}, [item_from, to_item]) => {
// Get route // Get route
let route = getters.sharedDetail && ! getters.sharedDetail.protected let route = getters.sharedDetail && ! getters.sharedDetail.protected
@@ -19,10 +20,14 @@ const actions = {
.then(() => { .then(() => {
commit('REMOVE_ITEM', item_from.unique_id) commit('REMOVE_ITEM', item_from.unique_id)
commit('INCREASE_FOLDER_ITEM', to_item.unique_id) commit('INCREASE_FOLDER_ITEM', to_item.unique_id)
if (item_from.type === 'folder' && getters.currentFolder.location !== 'public')
dispatch('getAppData')
}) })
.catch(() => isSomethingWrong()) .catch(() => isSomethingWrong())
}, },
createFolder: ({commit, getters}, folderName) => { createFolder: ({commit, getters, dispatch}, folderName) => {
// Get route // Get route
let route = getters.sharedDetail && ! getters.sharedDetail.protected let route = getters.sharedDetail && ! getters.sharedDetail.protected
@@ -36,10 +41,14 @@ const actions = {
}) })
.then(response => { .then(response => {
commit('ADD_NEW_FOLDER', response.data) commit('ADD_NEW_FOLDER', response.data)
if ( getters.currentFolder.location !== 'public' ) {
dispatch('getAppData')
}
}) })
.catch(() => isSomethingWrong()) .catch(() => isSomethingWrong())
}, },
renameItem: ({commit, getters}, data) => { renameItem: ({commit, getters, dispatch}, data) => {
// Updated name in favourites panel // Updated name in favourites panel
if (getters.permission === 'master' && data.type === 'folder') if (getters.permission === 'master' && data.type === 'folder')
@@ -57,6 +66,9 @@ const actions = {
}) })
.then(response => { .then(response => {
commit('CHANGE_ITEM_NAME', response.data) commit('CHANGE_ITEM_NAME', response.data)
if (data.type === 'folder' && getters.currentFolder.location !== 'public')
dispatch('getAppData')
}) })
.catch(() => isSomethingWrong()) .catch(() => isSomethingWrong())
}, },
@@ -87,9 +99,6 @@ const actions = {
commit('UPLOADING_FILE_PROGRESS', 0) commit('UPLOADING_FILE_PROGRESS', 0)
if (getters.permission === 'master')
commit('UPDATE_RECENT_UPLOAD', response.data)
resolve(response) resolve(response)
}) })
.catch(error => { .catch(error => {
@@ -121,7 +130,7 @@ const actions = {
}) })
.catch(() => isSomethingWrong()) .catch(() => isSomethingWrong())
}, },
deleteItem: ({commit, getters}, data) => { deleteItem: ({commit, getters, dispatch}, data) => {
// Remove file // Remove file
commit('REMOVE_ITEM', data.unique_id) commit('REMOVE_ITEM', data.unique_id)
@@ -131,8 +140,6 @@ const actions = {
if (data.type === 'folder') if (data.type === 'folder')
commit('REMOVE_ITEM_FROM_FAVOURITES', data) commit('REMOVE_ITEM_FROM_FAVOURITES', data)
else
commit('REMOVE_ITEM_FROM_RECENT_UPLOAD', data.unique_id)
} }
// Remove file preview // Remove file preview
@@ -150,18 +157,30 @@ const actions = {
force_delete: data.deleted_at ? true : false force_delete: data.deleted_at ? true : false
} }
}) })
.then(() => {
// If is folder, update app data
if (data.type === 'folder' && data.unique_id === getters.currentFolder.unique_id) {
if ( getters.currentFolder.location === 'public' ) {
dispatch('browseShared', [{folder: last(getters.browseHistory), back: true, init: false}])
} else {
dispatch('getFolder', [{folder: last(getters.browseHistory), back: true, init: false}])
dispatch('getAppData')
}
}
})
.catch(() => isSomethingWrong()) .catch(() => isSomethingWrong())
}, },
emptyTrash: ({commit, getters}) => { emptyTrash: ({commit, getters}) => {
// Clear file browser // Clear file browser
commit('FLUSH_DATA') commit('LOADING_STATE', {loading: true, data: []})
commit('LOADING_STATE', true)
axios axios
.delete(getters.api + '/empty-trash') .delete(getters.api + '/empty-trash')
.then(() => { .then(() => {
commit('LOADING_STATE', false) commit('LOADING_STATE', {loading: false, data: []})
events.$emit('scrollTop') events.$emit('scrollTop')
// Remove file preview // Remove file preview
+14 -21
View File
@@ -20,9 +20,11 @@ const defaultState = {
sharedFile: undefined, sharedFile: undefined,
} }
const actions = { const actions = {
browseShared: ({commit, state, getters}, [folder, back = false, init = false]) => { browseShared: ({commit, getters}, [payload]) => {
commit('LOADING_STATE', true) commit('LOADING_STATE', {loading: true, data: []})
commit('FLUSH_DATA')
if (payload.init)
commit('FLUSH_FOLDER_HISTORY')
// Clear search // Clear search
if (getters.isSearching) { if (getters.isSearching) {
@@ -30,35 +32,26 @@ const actions = {
events.$emit('clear-query') events.$emit('clear-query')
} }
// Create current folder for history if (! payload.back)
let currentFolder = { commit('STORE_PREVIOUS_FOLDER', getters.currentFolder)
name: folder.name,
unique_id: folder.unique_id, payload.folder.location = 'public'
location: 'public'
}
let route = getters.sharedDetail.protected let route = getters.sharedDetail.protected
? '/api/folders/' + currentFolder.unique_id + '/private' ? '/api/folders/' + payload.folder.unique_id + '/private'
: '/api/folders/' + currentFolder.unique_id + '/public/' + router.currentRoute.params.token +'/' : '/api/folders/' + payload.folder.unique_id + '/public/' + router.currentRoute.params.token +'/'
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axios axios
.get(route) .get(route)
.then(response => { .then(response => {
commit('LOADING_STATE', {loading: false, data: response.data})
commit('LOADING_STATE', false) commit('STORE_CURRENT_FOLDER', payload.folder)
commit('GET_DATA', response.data)
commit('STORE_CURRENT_FOLDER', currentFolder)
events.$emit('scrollTop') events.$emit('scrollTop')
if (back) { if (payload.back)
commit('REMOVE_BROWSER_HISTORY') commit('REMOVE_BROWSER_HISTORY')
} else {
if (!init)
commit('ADD_BROWSER_HISTORY', currentFolder)
}
resolve(response) resolve(response)
}) })
.catch((error) => { .catch((error) => {
-7
View File
@@ -80,9 +80,6 @@ const mutations = {
SET_PERMISSION(state, role) { SET_PERMISSION(state, role) {
state.permission = role state.permission = role
}, },
SET_AUTHORIZED(state, data) {
state.authorized = data
},
DESTROY_DATA(state) { DESTROY_DATA(state) {
state.authorized = false state.authorized = false
state.app = undefined state.app = undefined
@@ -101,16 +98,12 @@ const mutations = {
state.app.user.avatar = avatar state.app.user.avatar = avatar
}, },
UPDATE_RECENT_UPLOAD(state, file) { UPDATE_RECENT_UPLOAD(state, file) {
// Remove last file from altest uploads // Remove last file from altest uploads
if (state.app.latest_uploads.length === 7) state.app.latest_uploads.pop() if (state.app.latest_uploads.length === 7) state.app.latest_uploads.pop()
// Add new file to latest uploads // Add new file to latest uploads
state.app.latest_uploads.unshift(file) state.app.latest_uploads.unshift(file)
}, },
REMOVE_ITEM_FROM_RECENT_UPLOAD(state, unique_id) {
state.app.latest_uploads = state.app.latest_uploads.filter(file => file.unique_id !== unique_id)
},
REMOVE_ITEM_FROM_FAVOURITES(state, item) { REMOVE_ITEM_FROM_FAVOURITES(state, item) {
state.app.favourites = state.app.favourites.filter(folder => folder.unique_id !== item.unique_id) state.app.favourites = state.app.favourites.filter(folder => folder.unique_id !== item.unique_id)
}, },
@@ -171,7 +171,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_auth-form';
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
</style> </style>
@@ -122,7 +122,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_auth-form';
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
</style> </style>
+3 -4
View File
@@ -86,8 +86,8 @@
return { return {
isLoading: false, isLoading: false,
checkedAccount: undefined, checkedAccount: undefined,
loginPassword: '', loginPassword: 'secret',
loginEmail: '', loginEmail: 'howdy@hi5ve.digital',
} }
}, },
methods: { methods: {
@@ -208,7 +208,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_auth-form';
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
</style> </style>
+1 -2
View File
@@ -172,7 +172,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_auth-form';
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
</style> </style>
+178
View File
@@ -0,0 +1,178 @@
<template>
<section id="viewport" v-if="app">
<ContentSidebar>
<!--Locations-->
<ContentGroup title="Base">
<div class="menu-list-wrapper">
<a class="menu-list-item link" :class="{'is-active': $isThisLocation(['base'])}" @click="goHome">
<div class="icon">
<home-icon size="17"></home-icon>
</div>
<div class="label">
Home
</div>
</a>
<a class="menu-list-item link" :class="{'is-active': $isThisLocation(['latest'])}"
@click="getLatest">
<div class="icon">
<upload-cloud-icon size="17"></upload-cloud-icon>
</div>
<div class="label">
Recent Uploads
</div>
</a>
</div>
</ContentGroup>
<!--Navigator-->
<ContentGroup title="Navigator" class="navigator">
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="items" v-for="items in app.tree"
:key="items.unique_id"/>
</ContentGroup>
<!--Favourites-->
<ContentGroup :title="$t('sidebar.favourites')">
<div class="menu-list-wrapper favourites"
:class="{ 'is-dragenter': area }"
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish($event)"
>
<transition-group tag="div" class="menu-list" name="folder-item">
<a class="empty-list" v-if="app.favourites.length == 0" :key="0">
{{ $t('sidebar.favourites_empty') }}
</a>
<a @click.stop="openFolder(folder)"
class="menu-list-item"
:class="{'is-current': currentFolder.unique_id === folder.unique_id}"
v-for="folder in app.favourites"
:key="folder.unique_id">
<div>
<folder-icon size="17" class="folder-icon"></folder-icon>
<span class="label">{{ folder.name }}</span>
</div>
<x-icon size="17" @click.stop="removeFavourite(folder)" class="delete-icon"></x-icon>
</a>
</transition-group>
</div>
</ContentGroup>
</ContentSidebar>
<ContentFileView/>
</section>
</template>
<script>
import TreeMenuNavigator from '@/components/Others/TreeMenuNavigator'
import ContentFileView from '@/components/Others/ContentFileView'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import ContentGroup from '@/components/Sidebar/ContentGroup'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import {
UploadCloudIcon,
FolderIcon,
HomeIcon,
XIcon,
} from 'vue-feather-icons'
export default {
name: 'FilesView',
components: {
TreeMenuNavigator,
ContentFileView,
ContentSidebar,
UploadCloudIcon,
ContentGroup,
FolderIcon,
HomeIcon,
XIcon,
},
computed: {
...mapGetters(['app', 'homeDirectory', 'currentFolder']),
},
data() {
return {
area: false,
draggedItem: undefined,
}
},
methods: {
getLatest() {
this.$store.dispatch('getLatest')
},
goHome() {
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
},
openFolder(folder) {
this.$store.dispatch('getFolder', [{folder: folder, back: false, init: false}])
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
this.area = true
},
dragLeave() {
this.area = false
},
dragFinish() {
this.area = false
// Check if draged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if folder exist in favourites
if (this.app.favourites.find(folder => folder.unique_id == this.draggedItem.unique_id)) return
// Store favourites folder
this.$store.dispatch('addToFavourites', this.draggedItem)
},
removeFavourite(folder) {
this.$store.dispatch('removeFromFavourites', folder)
}
},
created() {
this.goHome()
// Listen for dragstart folder items
events.$on('dragstart', (item) => this.draggedItem = item)
}
}
</script>
<style lang="scss" scoped>
.navigator {
width: 100%;
overflow-x: auto;
}
// Transition
.folder-item-move {
transition: transform 300s ease;
}
.folder-item-enter-active {
transition: all 300ms ease;
}
.folder-item-leave-active {
transition: all 300ms;
}
.folder-item-enter, .folder-item-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(30px);
}
.folder-item-leave-active {
position: absolute;
}
</style>
@@ -0,0 +1,67 @@
<template>
<section id="viewport">
<ContentSidebar>
<!--Navigator-->
<ContentGroup title="Base">
<div class="menu-list-wrapper">
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['shared'])}" @click="getShared()">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="label">
My Shared Items
</div>
</li>
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['participant_uploads'])}" @click="getParticipantUploads()">
<div class="icon">
<users-icon size="17"></users-icon>
</div>
<div class="label">
Participant Uploads
</div>
</li>
</div>
</ContentGroup>
</ContentSidebar>
<ContentFileView />
</section>
</template>
<script>
import ContentFileView from '@/components/Others/ContentFileView'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import ContentGroup from '@/components/Sidebar/ContentGroup'
import {
LinkIcon,
UsersIcon,
} from 'vue-feather-icons'
export default {
name: 'FilesView',
components: {
ContentFileView,
ContentSidebar,
ContentGroup,
LinkIcon,
UsersIcon,
},
methods: {
getShared() {
this.$store.dispatch('getShared', [{back: false, init: false}])
},
getParticipantUploads() {
this.$store.dispatch('getParticipantUploads')
},
},
mounted() {
this.getShared()
}
}
</script>
<style lang="scss" scoped>
</style>
+51
View File
@@ -0,0 +1,51 @@
<template>
<section id="viewport">
<ContentSidebar>
<!--Tools-->
<ContentGroup title="Tools" class="navigator">
<div class="menu-list-wrapper">
<div class="menu-list-item link" @click="emptyTrash()">
<div class="icon">
<trash-icon size="17"></trash-icon>
</div>
<div class="label">
Empty Trash
</div>
</div>
</div>
</ContentGroup>
</ContentSidebar>
<ContentFileView/>
</section>
</template>
<script>
import ContentFileView from '@/components/Others/ContentFileView'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import ContentGroup from '@/components/Sidebar/ContentGroup'
import {
TrashIcon,
} from 'vue-feather-icons'
export default {
name: 'FilesView',
components: {
ContentFileView,
ContentSidebar,
ContentGroup,
TrashIcon,
},
methods: {
emptyTrash() {
this.$store.dispatch('emptyTrash')
},
},
created() {
this.$store.dispatch('getTrash')
}
}
</script>
<style lang="scss" scoped>
</style>
-253
View File
@@ -1,253 +0,0 @@
<template>
<div @click="fileViewClick" @contextmenu.prevent.capture="contextMenu($event, undefined)" id="files-view" :class="filesViewWidth">
<ContextMenu />
<DesktopToolbar />
<FileBrowser />
</div>
</template>
<script>
import DesktopToolbar from '@/components/FilesView/DesktopToolbar'
import FileBrowser from '@/components/FilesView/FileBrowser'
import ContextMenu from '@/components/FilesView/ContextMenu'
import {ResizeSensor} from 'css-element-queries'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilesView',
components: {
DesktopToolbar,
FileBrowser,
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() {
let homeDirectory = {
name: this.$t('locations.home'),
location: 'base',
unique_id: 0,
}
// Set start directory
this.$store.commit('SET_START_DIRECTORY', homeDirectory)
// Load folder
this.$store.dispatch('getFolder', [homeDirectory, false, true])
var filesView = document.getElementById('files-view');
new ResizeSensor(filesView, this.handleContentResize);
}
}
</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,94 @@
<template>
<div class="page">
<MobileHeader />
<nav class="mobile-navigation">
<!--Navigation-->
<MenuItemList :navigation="navigation" />
</nav>
</div>
</template>
<script>
import MenuItemList from '@/components/Mobile/MenuItemList'
import MobileHeader from '@/components/Mobile/MobileHeader'
export default {
name: 'MobileSettings',
components: {
MenuItemList,
MobileHeader,
},
data() {
return {
navigation: [
{
icon: 'hard-drive',
title: 'Profile Settings',
routeName: 'Profile',
},
{
icon: 'latest',
title: 'Password',
routeName: 'Password',
},
]
}
},
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.page {
width: 100%;
}
.mobile-navigation {
padding: 0 20px;
width: 100%;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
}
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) {
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
</style>
+70
View File
@@ -0,0 +1,70 @@
<template>
<section id="viewport">
<ContentSidebar>
<!--User Headline-->
<UserHeadline class="user-headline"/>
<!--Locations-->
<ContentGroup title="Menu" class="navigator">
<div class="menu-list-wrapper">
<router-link :to="{name: 'Profile'}" class="menu-list-item link">
<div class="icon">
<user-icon size="17"></user-icon>
</div>
<div class="label">
Profile Settings
</div>
</router-link>
<router-link :to="{name: 'Password'}" class="menu-list-item link">
<div class="icon">
<lock-icon size="17"></lock-icon>
</div>
<div class="label">
Password
</div>
</router-link>
<!--<router-link :to="{name: 'Profile'}" class="menu-list-item link">
<div class="icon">
<hard-drive-icon size="17"></hard-drive-icon>
</div>
<div class="label">
Storage
</div>
</router-link>-->
</div>
</ContentGroup>
</ContentSidebar>
<router-view/>
</section>
</template>
<script>
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import ContentGroup from '@/components/Sidebar/ContentGroup'
import UserHeadline from '@/components/Sidebar/UserHeadline'
import {
UserIcon,
LockIcon,
} from 'vue-feather-icons'
export default {
name: 'Settings',
components: {
ContentSidebar,
UserHeadline,
ContentGroup,
UserIcon,
LockIcon,
},
}
</script>
<style lang="scss" scoped>
.user-headline {
margin-bottom: 38px;
}
</style>
@@ -47,7 +47,5 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_forms';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
</style> </style>
+7 -26
View File
@@ -38,7 +38,7 @@
</div> </div>
<!--File browser--> <!--File browser-->
<div v-if="currentPage === 'page-files'" id="files-view" :class="filesViewWidth"> <div v-if="currentPage === 'page-files'" id="files-view">
<div id="single-file" v-if="sharedDetail.type === 'file'"> <div id="single-file" v-if="sharedDetail.type === 'file'">
<div class="single-file-wrapper"> <div class="single-file-wrapper">
<FileItemGrid v-if="sharedFile" :data="sharedFile" :context-menu="false"/> <FileItemGrid v-if="sharedFile" :data="sharedFile" :context-menu="false"/>
@@ -103,7 +103,7 @@
Alert, Alert,
}, },
computed: { computed: {
...mapGetters(['config', 'filesViewWidth', 'sharedDetail', 'sharedFile', 'appSize']), ...mapGetters(['config', 'sharedDetail', 'sharedFile', 'appSize']),
}, },
data() { data() {
return { return {
@@ -164,16 +164,8 @@
location: 'public', location: 'public',
} }
// Set start directory
this.$store.commit('SET_START_DIRECTORY', homeDirectory)
// Load folder // Load folder
this.$store.dispatch('browseShared', [homeDirectory, false, true]) this.$store.dispatch('browseShared', [{folder: homeDirectory, back: false, init: true}])
.then(() => {
var filesView = document.getElementById('files-view')
new ResizeSensor(filesView, this.handleContentResize)
})
} }
// Get file // Get file
@@ -190,18 +182,6 @@
contextMenu(event, item) { contextMenu(event, item) {
events.$emit('contextMenu:show', 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')
},
}, },
created() { created() {
@@ -235,9 +215,10 @@
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_forms'; @import '@assets/vue-file-manager/_mixins';
@import '@assets/vue-file-manager/_auth-form';
@import '@assets/vue-file-manager/_auth'; @import '@assets/vue-file-manager/_auth';
#shared { #shared {
@@ -1,52 +1,20 @@
<template> <template>
<div id="user-settings"> <div id="user-settings">
<PageHeader :title="$t('profile.page_title')" /> <MobileHeader />
<PageHeader title="Password Settings"/>
<div class="content-page"> <div class="content-page">
<div class="avatar-upload">
<UserImageInput
v-model="avatar"
:avatar="app.user.avatar"
/>
<div class="info">
<span class="description">{{ $t('profile.photo_description') }}</span>
<span class="supported">{{ $t('profile.photo_supported') }}</span>
</div>
</div>
<ValidationObserver ref="account" v-slot="{ invalid }" tag="form" class="form block-form">
<ThemeLabel>{{ $t('profile.profile_info') }}</ThemeLabel>
<div class="block-wrapper">
<label>{{ $t('page_registration.label_email') }}</label>
<div class="input-wrapper">
<input :value="app.user.email" :placeholder="$t('page_registration.placeholder_email')" type="email" disabled/>
</div>
</div>
<div class="block-wrapper">
<label>{{ $t('page_registration.label_name') }}</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Full Name" rules="required"
v-slot="{ errors }">
<input @keyup="$updateText('/user/profile', 'name', name)" v-model="name"
:placeholder="$t('page_registration.placeholder_name')" type="text"
:class="{'is-error': errors[0]}"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
</ValidationObserver>
<ValidationObserver ref="password" @submit.prevent="resetPassword" v-slot="{ invalid }" tag="form" <ValidationObserver ref="password" @submit.prevent="resetPassword" v-slot="{ invalid }" tag="form"
class="form block-form"> class="form block-form">
<ThemeLabel>{{ $t('profile.change_pass') }}</ThemeLabel>
<div class="block-wrapper"> <div class="block-wrapper">
<label>{{ $t('page_create_password.label_new_pass') }}:</label> <label>{{ $t('page_create_password.label_new_pass') }}:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="New Password" <ValidationProvider tag="div" mode="passive" class="input-wrapper" name="New Password"
rules="required" v-slot="{ errors }"> rules="required" v-slot="{ errors }">
<input v-model="newPassword" :placeholder="$t('page_create_password.label_new_pass')" type="password" <input v-model="newPassword" :placeholder="$t('page_create_password.label_new_pass')"
type="password"
:class="{'is-error': errors[0]}"/> :class="{'is-error': errors[0]}"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span> <span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider> </ValidationProvider>
@@ -56,7 +24,8 @@
<label>{{ $t('page_create_password.label_confirm_pass') }}:</label> <label>{{ $t('page_create_password.label_confirm_pass') }}:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Confirm Your Password" <ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Confirm Your Password"
rules="required" v-slot="{ errors }"> rules="required" v-slot="{ errors }">
<input v-model="newPasswordConfirmation" :placeholder="$t('page_create_password.label_confirm_pass')" type="password" <input v-model="newPasswordConfirmation"
:placeholder="$t('page_create_password.label_confirm_pass')" type="password"
:class="{'is-error': errors[0]}"/> :class="{'is-error': errors[0]}"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span> <span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider> </ValidationProvider>
@@ -75,12 +44,12 @@
<script> <script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full' import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import UserImageInput from '@/components/Others/UserImageInput' import UserImageInput from '@/components/Others/UserImageInput'
import MobileHeader from '@/components/Mobile/MobileHeader'
import ButtonBase from '@/components/FilesView/ButtonBase' import ButtonBase from '@/components/FilesView/ButtonBase'
import PageHeader from '@/components/Others/PageHeader' import PageHeader from '@/components/Others/PageHeader'
import ThemeLabel from '@/components/Others/ThemeLabel' import ThemeLabel from '@/components/Others/ThemeLabel'
import {required} from 'vee-validate/dist/rules' import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from '@/bus' import {events} from '@/bus'
import axios from 'axios' import axios from 'axios'
@@ -90,6 +59,7 @@
ValidationProvider, ValidationProvider,
ValidationObserver, ValidationObserver,
UserImageInput, UserImageInput,
MobileHeader,
PageHeader, PageHeader,
ButtonBase, ButtonBase,
ThemeLabel, ThemeLabel,
@@ -98,19 +68,10 @@
computed: { computed: {
...mapGetters(['app']), ...mapGetters(['app']),
}, },
watch: {
name: debounce(function (val) {
if (val === '') return
this.$store.commit('UPDATE_NAME', val)
}, 300),
},
data() { data() {
return { return {
newPasswordConfirmation: '', newPasswordConfirmation: '',
newPassword: '', newPassword: '',
avatar: undefined,
name: '',
} }
}, },
methods: { methods: {
@@ -124,9 +85,9 @@
// Send request to get user reset link // Send request to get user reset link
axios axios
.post(this.$store.getters.api + '/user/password', { .post(this.$store.getters.api + '/user/password', {
password: this.newPassword, password: this.newPassword,
password_confirmation: this.newPasswordConfirmation, password_confirmation: this.newPasswordConfirmation,
}) })
.then(() => { .then(() => {
// Reset inputs // Reset inputs
@@ -155,67 +116,15 @@
} }
}) })
} }
},
created() {
this.name = this.app.user.name
this.avatar = this.app.user.avatar
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
@import "@assets/app.scss"; @import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import '@assets/vue-file-manager/_forms'; @import '@assets/vue-file-manager/_forms';
.avatar-upload {
display: flex;
align-items: center;
margin-top: 20px;
.info {
margin-left: 25px;
.description {
@include font-size(18);
font-weight: 700;
color: $text;
}
.supported {
display: block;
@include font-size(12);
font-weight: 500;
color: $light_text;
}
}
}
.form {
.confirm-form {
margin-top: 30px;
text-align: right;
}
&.block-form {
margin-top: 50px;
max-width: 700px;
.block-wrapper {
justify-content: flex-start;
label {
text-align: left;
flex: 0 0 220px;
}
.input-wrapper, input {
width: 100%;
}
}
}
}
#user-settings { #user-settings {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
@@ -223,11 +132,12 @@
position: relative; position: relative;
.content-page { .content-page {
padding-left: 30px;
padding-right: 30px;
overflow-y: auto; overflow-y: auto;
height: 100%; height: 100%;
padding-bottom: 100px; padding-bottom: 100px;
max-width: 700px;
width: 100%;
margin: 0 auto;
} }
} }
@@ -241,10 +151,6 @@
} }
} }
.avatar-upload {
margin-top: 30px;
}
.form { .form {
.button-base { .button-base {
width: 100%; width: 100%;
@@ -256,16 +162,6 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.avatar-upload .info {
.description {
color: $dark_mode_text_primary;
}
.supported {
color: $dark_mode_text_secondary;
}
}
} }
</style> </style>
+170
View File
@@ -0,0 +1,170 @@
<template>
<div id="user-settings" v-if="app">
<MobileHeader />
<PageHeader :title="$t('profile.page_title')" />
<div class="content-page">
<div class="avatar-upload">
<UserImageInput
v-model="avatar"
:avatar="app.user.avatar"
/>
<div class="info">
<span class="description">{{ $t('profile.photo_description') }}</span>
<span class="supported">{{ $t('profile.photo_supported') }}</span>
</div>
</div>
<ValidationObserver ref="account" v-slot="{ invalid }" tag="form" class="form block-form">
<div class="block-wrapper">
<label>{{ $t('page_registration.label_email') }}</label>
<div class="input-wrapper">
<input :value="app.user.email" :placeholder="$t('page_registration.placeholder_email')" type="email" disabled/>
</div>
</div>
<div class="block-wrapper">
<label>{{ $t('page_registration.label_name') }}</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Full Name" rules="required"
v-slot="{ errors }">
<input @keyup="$updateText('/user/profile', 'name', name)" v-model="name"
:placeholder="$t('page_registration.placeholder_name')" type="text"
:class="{'is-error': errors[0]}"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
</ValidationObserver>
</div>
</div>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import UserImageInput from '@/components/Others/UserImageInput'
import MobileHeader from '@/components/Mobile/MobileHeader'
import ButtonBase from '@/components/FilesView/ButtonBase'
import PageHeader from '@/components/Others/PageHeader'
import ThemeLabel from '@/components/Others/ThemeLabel'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from '@/bus'
import axios from 'axios'
export default {
name: 'Profile',
components: {
ValidationProvider,
ValidationObserver,
UserImageInput,
MobileHeader,
PageHeader,
ButtonBase,
ThemeLabel,
required,
},
computed: {
...mapGetters(['app']),
},
watch: {
name: debounce(function (val) {
if (val === '') return
this.$store.commit('UPDATE_NAME', val)
}, 300),
},
data() {
return {
avatar: undefined,
name: '',
}
},
created() {
if (this.app) {
this.name = this.app.user.name
this.avatar = this.app.user.avatar
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import '@assets/vue-file-manager/_forms';
.avatar-upload {
display: flex;
align-items: center;
margin-bottom: 30px;
.info {
margin-left: 25px;
.description {
@include font-size(15);
font-weight: 700;
color: $text;
}
.supported {
display: block;
@include font-size(12);
font-weight: 500;
color: $light_text;
}
}
}
#user-settings {
overflow: hidden;
width: 100%;
height: 100%;
position: relative;
.content-page {
overflow-y: auto;
height: 100%;
padding-bottom: 100px;
max-width: 700px;
width: 100%;
margin: 0 auto;
}
}
@media only screen and (max-width: 960px) {
#user-settings {
.content-page {
padding-left: 15px;
padding-right: 15px;
}
}
.form {
.button-base {
width: 100%;
margin-top: 0;
text-align: center;
}
}
}
@media (prefers-color-scheme: dark) {
.avatar-upload .info {
.description {
color: $dark_mode_text_primary;
}
.supported {
color: $dark_mode_text_secondary;
}
}
}
</style>
+200 -5
View File
@@ -1,10 +1,13 @@
// Fonts // Fonts
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;900&display=swap');
// Variables @import '@assets/vue-file-manager/_variables';
@import 'vue-file-manager/_variables'; @import '@assets/vue-file-manager/_mixins';
@import 'vue-file-manager/_mixins';
//@import 'vue-file-manager/_forms'; #viewport {
display: flex;
width: 100%;
@include transition(200ms);
}
#application-wrapper { #application-wrapper {
display: flex; display: flex;
@@ -14,4 +17,196 @@
position: relative; position: relative;
width: 100%; width: 100%;
} }
}
.menu-list-wrapper {
margin-bottom: 20px;
.menu-list-item {
display: block;
padding: 12px 15px 12px 25px;
text-decoration: none;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
&.link {
display: flex;
align-items: center;
&.is-active,
&.router-link-exact-active,
&:hover {
svg {
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.label {
color: $theme;
}
}
.icon {
margin-right: 12px;
line-height: 0;
path, line, polyline, rect, circle {
stroke: $text;
}
}
.text-label {
@include font-size(16);
}
}
&:hover,
&.is-active {
.delete-icon {
display: block;
}
}
.folder-icon {
line-height: 0;
width: 15px;
margin-right: 9px;
vertical-align: middle;
margin-top: -1px;
}
.delete-icon {
display: none;
position: absolute;
right: 15px;
top: 50%;
@include transform(translateY(-50%));
}
.label {
@include font-size(13);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
color: $text;
}
}
&.favourites {
&.is-dragenter .menu-list {
border: 2px dashed $theme;
border-radius: 8px;
}
.menu-list {
border: 2px dashed transparent;
.menu-list-item {
padding: 8px 23px;
.icon {
margin-right: 5px;
width: 20px;
path, line, polyline, rect, circle {
@include transition(150ms);
}
}
&:hover,
&.is-selected,
&.is-current {
.folder-icon {
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.label {
color: $theme;
}
}
}
}
}
.empty-list {
@include font-size(12);
color: $text-muted;
display: block;
padding: 15px;
}
}
@media only screen and (max-width: 1024px) {
.menu-list-wrapper {
.menu-list-item {
padding: 12px 15px 12px 20px;
}
&.favourites {
.menu-list .menu-list-item {
padding: 8px 18px;
}
}
}
}
@media (prefers-color-scheme: dark) {
.menu-list-wrapper {
.menu-list-item {
&.link {
.icon {
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
.label {
color: $dark_mode_text_primary;
}
.icon {
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
&:hover {
background: rgba($theme, .1);
.label {
color: $theme;
}
.icon {
path, line, polyline, rect, circle {
stroke: $theme;
}
}
}
}
}
} }
+213
View File
@@ -0,0 +1,213 @@
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.form {
&.inline-form {
display: flex;
position: relative;
justify-content: center;
.input-wrapper {
position: relative;
.error-message {
position: absolute;
left: 0;
bottom: -25px;
}
}
}
&.block-form {
&.create-new-password {
.block-wrapper label {
width: 280px;
}
}
.block-wrapper {
display: flex;
align-items: center;
margin-top: 25px;
justify-content: center;
&:first-child {
margin-top: 0;
}
label {
white-space: nowrap;
@include font-size(18);
font-weight: 700;
padding-right: 20px;
width: 200px;
text-align: right !important;
color: $text;
}
}
.button {
margin-top: 50px;
}
}
}
.input-wrapper {
.error-message {
@include font-size(14);
color: $danger;
padding-top: 5px;
display: block;
text-align: left;
}
}
textarea {
width: 100%;
}
textarea,
input[type="password"],
input[type="text"],
input[type="email"] {
background: $light_background;
border: 1px solid transparent;
transition: 0.15s all ease;
@include font-size(16);
border-radius: 8px;
padding: 13px 20px;
appearance: none;
font-weight: 700;
outline: 0;
width: 100%;
&.is-error {
border-color: $danger;
box-shadow: 0 0 7px rgba($danger, 0.3);
}
&::placeholder {
color: $light_text;
@include font-size(16);
}
&:focus {
border-color: $theme;
box-shadow: 0 0 7px rgba($theme, 0.3);
}
&[disabled] {
color: $light_text;
cursor: not-allowed;
}
}
.additional-link {
@include font-size(16);
margin-top: 50px;
display: block;
color: $text;
b, a {
color: $theme;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
@media only screen and (max-width: 960px) {
.form {
.button {
margin-top: 20px;
width: 100%;
margin-left: 0;
margin-right: 0;
}
input, textarea {
width: 100%;
min-width: 100%;
}
&.block-form {
.block-wrapper {
display: block;
label {
width: 100%;
padding-right: 0;
display: block;
margin-bottom: 7px;
text-align: left !important;
@include font-size(14);
padding-top: 0;
}
}
.button {
margin-top: 25px;
margin-left: 0;
margin-right: 0;
}
}
&.inline-form {
display: block;
.input-wrapper .error-message {
position: relative;
bottom: 0;
}
}
.button {
padding: 14px 32px;
}
}
textarea,
input[type="password"],
input[type="text"],
input[type="email"] {
padding: 14px 20px;
}
}
@media (prefers-color-scheme: dark) {
.form {
&.block-form {
.block-wrapper label {
color: $dark_mode_text_primary;
}
}
}
textarea,
input[type="password"],
input[type="text"],
input[type="email"] {
background: $dark_mode_foreground;
color: $dark_mode_text_primary;
&::placeholder {
color: $dark_mode_text_secondary;
}
&[disabled] {
color: rgba($dark_mode_text_secondary, 15%);
}
}
}
+3
View File
@@ -1,3 +1,6 @@
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.auth-form { .auth-form {
text-align: center; text-align: center;
max-width: 600px; max-width: 600px;
+16 -26
View File
@@ -1,4 +1,8 @@
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.form { .form {
max-width: 700px;
&.inline-form { &.inline-form {
display: flex; display: flex;
@@ -18,30 +22,15 @@
&.block-form { &.block-form {
&.create-new-password {
.block-wrapper label {
width: 280px;
}
}
.block-wrapper { .block-wrapper {
display: flex; margin-bottom: 20px;
align-items: center;
margin-top: 25px;
justify-content: center;
&:first-child {
margin-top: 0;
}
label { label {
white-space: nowrap; @include font-size(12);
@include font-size(18); color: #AFAFAF;
font-weight: 700; font-weight: 700;
padding-right: 20px; display: block;
width: 200px; margin-bottom: 5px;
text-align: right !important;
color: $text;
} }
} }
@@ -70,16 +59,16 @@ textarea,
input[type="password"], input[type="password"],
input[type="text"], input[type="text"],
input[type="email"] { input[type="email"] {
background: $light_background;
border: 1px solid transparent; border: 1px solid transparent;
transition: 0.15s all ease; @include transition(150ms);
@include font-size(16); @include font-size(15);
border-radius: 8px; border-radius: 8px;
padding: 13px 20px; padding: 13px 20px;
appearance: none; appearance: none;
font-weight: 700; font-weight: 700;
outline: 0; outline: 0;
width: 100%; width: 100%;
background: $light_mode_input_background;
&.is-error { &.is-error {
border-color: $danger; border-color: $danger;
@@ -87,8 +76,9 @@ input[type="email"] {
} }
&::placeholder { &::placeholder {
color: $light_text; //color: $light_text;
@include font-size(16); color: rgba($text, 0.5);
@include font-size(15);
} }
&:focus { &:focus {
@@ -97,7 +87,7 @@ input[type="email"] {
} }
&[disabled] { &[disabled] {
color: $light_text; background: hsl(0, 0%, 98%);
cursor: not-allowed; cursor: not-allowed;
} }
} }
+25 -7
View File
@@ -1,3 +1,6 @@
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
// Forms // Forms
.form-wrapper { .form-wrapper {
padding: 0 20px; padding: 0 20px;
@@ -34,29 +37,44 @@
} }
.icon { .icon {
background: $theme; background: black;
padding: 14px 18px; padding: 15px 18px;
border-top-right-radius: 8px; border-top-right-radius: 8px;
border-bottom-right-radius: 8px; border-bottom-right-radius: 8px;
@include font-size(16);
text-align: center; text-align: center;
line-height: 0;
svg path { path, polyline {
fill: white; stroke: white;
} }
} }
} }
} }
.input-label { .input-label {
@include font-size(15); @include font-size(12);
color: $text; color: $text;
font-weight: 700; font-weight: 700;
margin-bottom: 5px;
display: block; display: block;
margin-bottom: 5px;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.inline-wrapper {
&.icon-append {
.icon {
background: rgba($theme, 0.1);
path, polyline {
stroke: $theme;
}
}
}
}
.input-label { .input-label {
color: $dark_mode_text_primary; color: $dark_mode_text_primary;
} }
+14 -7
View File
@@ -1,24 +1,31 @@
// Colors // Colors
$text: #1b2539; $text: #1c1d1f;
$text-muted: #667b90; $text-muted: #667b90;
$theme: #00BC7E; $theme: #00BC7E;
$light_mode_border: rgba(0, 0, 0, 0.02); $yellow: #FFBD2D;
$pink: #FE66A1;
$red: #FE6057;
$purple: #9D66FE;
$light_mode_border: #F8F8F8;
$danger: #fd397a; $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_input_background: hsl(0, 0%, 98%);
$light_mode_popup_shadow: 0 15px 50px 10px rgba(26,38,74,0.12); $light_mode_popup_shadow: 0 15px 50px 10px rgba(26,38,74,0.12);
$light_mode_vignette: rgba(9, 8, 12, 0.15); $light_mode_vignette: rgba(9, 8, 12, 0.35);
// Dark Mode // Dark Mode
$dark_mode_vignette: rgba(0, 0, 0, 0.3); $dark_mode_vignette: rgba(0, 0, 0, 0.3);
$dark_mode_background: #1a1f25; $dark_mode_background: #111314;
$dark_mode_foreground: #202733; $dark_mode_foreground: #1e2024;
$dark_mode_text_primary: #B8C4D0; $dark_mode_text_primary: #bec6cf;
$dark_mode_text_secondary: #667b90; $dark_mode_text_secondary: #79848f;
$dark_mode_vignette: rgba(22, 23, 27, 0.70); $dark_mode_vignette: rgba(22, 23, 27, 0.70);
$dark_mode_popup_shadow: 0 10px 30px rgba(0, 0, 0, .3); $dark_mode_popup_shadow: 0 10px 30px rgba(0, 0, 0, .3);
$dark_mode_border_color: rgba(255, 255, 255, 0.02); $dark_mode_border_color: rgba(255, 255, 255, 0.02);
+2 -1
View File
@@ -34,7 +34,6 @@ Route::group(['middleware' => ['api']], function () {
Route::get('/files/{token}/public', 'Sharing\FileSharingController@file_public'); Route::get('/files/{token}/public', 'Sharing\FileSharingController@file_public');
Route::get('/shared/{token}', 'FileFunctions\ShareController@show'); Route::get('/shared/{token}', 'FileFunctions\ShareController@show');
// User reset password // User reset password
Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail'); Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
Route::post('/password/reset', 'Auth\ResetPasswordController@reset'); Route::post('/password/reset', 'Auth\ResetPasswordController@reset');
@@ -54,10 +53,12 @@ Route::group(['middleware' => ['auth:api', 'auth.master', 'scope:master']], func
Route::get('/user', 'User\AccountController@user'); Route::get('/user', 'User\AccountController@user');
// Browse // Browse
Route::get('/participant-uploads', 'FileBrowser\BrowseController@participant_uploads');
Route::get('/file-detail/{unique_id}', 'FileBrowser\BrowseController@file_detail'); Route::get('/file-detail/{unique_id}', 'FileBrowser\BrowseController@file_detail');
Route::get('/navigation', 'FileBrowser\BrowseController@navigation_tree'); Route::get('/navigation', 'FileBrowser\BrowseController@navigation_tree');
Route::get('/folders/{unique_id}', 'FileBrowser\BrowseController@folder'); Route::get('/folders/{unique_id}', 'FileBrowser\BrowseController@folder');
Route::get('/shared-all', 'FileBrowser\BrowseController@shared'); Route::get('/shared-all', 'FileBrowser\BrowseController@shared');
Route::get('/latest', 'FileBrowser\BrowseController@latest');
Route::get('/search', 'FileBrowser\BrowseController@search'); Route::get('/search', 'FileBrowser\BrowseController@search');
Route::get('/trash', 'FileBrowser\BrowseController@trash'); Route::get('/trash', 'FileBrowser\BrowseController@trash');
+7
View File
@@ -23,4 +23,11 @@ mix.js('resources/js/main.js', 'public/js')
} }
}, },
}) })
.options({
hmrOptions: {
//host: '172.20.10.5',
//host: '192.168.1.131',
port: '8080'
},
})
.disableNotifications(); .disableNotifications();