Compare commits

..

27 Commits

Author SHA1 Message Date
Peter Papp
a90311593b - added not found page
- Fixed admin menu in regular license
- allow_homepage default setting in regular license
- frontend build
2021-03-21 11:16:36 +01:00
Peter Papp
db57bde4fc fixed file preview in single shared file 2021-03-12 14:59:47 +01:00
Peter Papp
92e02d8b57 - fixed file download links when you are using different disk location in filesystems.disks.local.root 2021-02-25 15:54:18 +01:00
Peter Papp
b42d480c91 - uploading fix 2021-02-25 15:25:59 +01:00
Peter Papp
c8a9f18265 Merge remote-tracking branch 'origin/upload-fix'
# Conflicts:
#	public/mix-manifest.json
2021-02-25 15:23:09 +01:00
Peter Papp
56b7f30d47 - uploading fix 2021-02-25 15:22:16 +01:00
Peter Papp
b8790a964b - uploading via files queue 2021-02-21 19:56:03 +01:00
Peter Papp
05f850ab2c - ability to change folder color in demo version 2021-02-20 20:34:12 +01:00
Peter Papp
c2868c051d - ability to change emoji in demo version 2021-02-20 17:07:07 +01:00
Peter Papp
28b39a79e6 - frontend build 2021-02-20 16:30:12 +01:00
Peter Papp
2dbd9dd62d Merge remote-tracking branch 'origin/version-v1.8.1'
# Conflicts:
#	resources/js/components/FilesView/FileItemGrid.vue
#	resources/js/components/FilesView/FileItemList.vue
#	resources/js/components/Others/ThumbnailItem.vue
2021-02-20 15:30:44 +01:00
Peter Papp
9dbce7a73a - autofocus disabled on mobile devices
- tabwrapper icon fix
2021-02-20 14:48:59 +01:00
Peter Papp
8ac5c8fd38 - css refactoring to grid for emoji picker
- frontend build
2021-02-20 12:07:55 +01:00
Milos Holba
2a105877f3 add Zip & Download folder to mobile menu, fix thumbnailItem for images without thumbnail, change color of file icon in mobile menu dark mode 2021-02-16 00:07:05 +01:00
Milos Holba
28efba5773 fix open Processing popup for download files 2021-02-15 12:37:06 +01:00
Milos Holba
60f02622da add default class for Folder in FolderIcon 2021-02-15 12:32:46 +01:00
Milos Holba
7eee7deba5 fix create folder on mobile 2021-02-15 10:00:39 +01:00
Peter Papp
ba0b2bd3b9 apple emojis 2021-02-13 12:05:47 +01:00
Peter Papp
86090b5870 folder color picker 2021-02-13 10:30:54 +01:00
Peter Papp
39681bc48a emoji ios styling for list 2021-02-13 09:29:49 +01:00
Milos Holba
96e1bdd99f make folder icon component 2021-02-12 20:43:33 +01:00
Milos Holba
eebeee6948 scss changes in SetFolderIcon, add translation to sk/cn 2021-02-11 20:14:27 +01:00
Milos Holba
7be02edead change base_path to public_path in get_emojis_list function 2021-02-11 16:44:16 +01:00
Milos Holba
d65c27091c change functionality of Emojis List to async load from backend 2021-02-10 19:45:59 +01:00
Milos Holba
6c3630085e test for iOS emojis 2021-02-09 20:42:48 +01:00
Milos Holba
f6dbb5e71e expansion functionality for the reset button in selected emoji input for possibility to set default icon 2021-02-09 19:20:21 +01:00
Peter Papp
d92bb50a03 upload progressbar UI 2021-02-07 09:19:28 +01:00
39 changed files with 1043 additions and 716 deletions

View File

@@ -246,4 +246,16 @@ class AppFunctionsController extends Controller
Artisan::call('config:clear');
Artisan::call('config:cache');
}
/**
* Get Emojis List from the server
*
* @return $emojisList
*/
public function get_emojis_list()
{
$emojisList = json_decode(file_get_contents(public_path('assets/emojis.json'), true));
return collect([$emojisList]);
}
}

View File

@@ -277,7 +277,7 @@ class FileAccessController extends Controller
"Content-Disposition" => "attachment; filename=" . $file_pretty_name,
];
return response()->download(storage_path('/app/file-manager/') . $file->basename, $file_pretty_name, $headers);
return response()->download(config('filesystems.disks.local.root') . '/file-manager/' . $file->basename, $file_pretty_name, $headers);
}
/**

View File

@@ -72,15 +72,17 @@ class Demo
if ($item) {
$item->name = $request->name;
$item->icon_emoji = $request->folder_icon['emoji'] ?? null;
$item->icon_color = $request->folder_icon['color'] ?? null;
return $item;
} else {
return [
'unique_id' => $request->unique_id,
'name' => $request->name,
'type' => $request->type,
'unique_id' => $request->unique_id,
'name' => $request->name,
'type' => $request->type,
];
}
}
@@ -126,7 +128,8 @@ class Demo
*
* @return ResponseFactory|\Illuminate\Http\Response
*/
public static function response_204() {
public static function response_204()
{
return response('Done!', 204);
}
@@ -136,7 +139,8 @@ class Demo
*
* @return ResponseFactory|\Illuminate\Http\Response
*/
public static function favourites($user) {
public static function favourites($user)
{
return $user->favourite_folders->makeHidden(['pivot']);
}

View File

@@ -33,23 +33,29 @@ class Editor
* @param $unique_id
* @param $shared
*/
public static function set_folder_icon ($folder_icon, $unique_id, $shared = null)
public static function set_folder_icon($folder_icon, $unique_id, $shared = null)
{
$user_id = is_null($shared) ? Auth::id() : $shared->user_id;
// Get folder
$folder = FileManagerFolder::where('user_id', $user_id)
->where('unique_id', $unique_id)
->first();
->where('unique_id', $unique_id)
->first();
// Set default folder icon
if ($folder_icon === 'default') {
$folder->icon_emoji = null;
$folder->icon_color = null;
}
// If request have emoji set folder icon emoji
if(isset($folder_icon['emoji'])) {
if (isset($folder_icon['emoji'])) {
$folder->icon_emoji = $folder_icon['emoji'];
$folder->icon_color = null;
}
// If request have color set folder icon color
if(isset($folder_icon['color'])) {
if (isset($folder_icon['color'])) {
$folder->icon_emoji = null;
$folder->icon_color = $folder_icon['color'];
}
@@ -133,7 +139,7 @@ class Editor
return Zip::create([
'user_id' => $shared->user_id ?? Auth::id(),
'shared_token' => $shared->token ?? null,
'basename' => $zip_name,
'basename' => $zip_name,
]);
}
@@ -451,8 +457,8 @@ class Editor
$limit = get_setting('upload_limit');
// File size handling
if( $limit && $file_size > format_bytes($limit)) abort(413);
if ($limit && $file_size > format_bytes($limit)) abort(413);
// If last then process file
if ($request->boolean('is_last')) {

View File

@@ -168,5 +168,9 @@ return [
'name' => 'footer_content',
'value' => '© 2021 Simple & Powerful Personal Cloud Storage. Developed by <a href="https://hi5ve.digital" target="_blank">Hi5Ve.Digital</a>',
],
[
'name' => 'allow_homepage',
'value' => 1,
],
],
];

View File

@@ -2,7 +2,7 @@
return [
'version' => '1.8.1',
'version' => '1.8.2.3',
// Define size of chunk uploaded by MB. E.g. integer 128 means chunk size will be 128MB.
'chunk_size' => env('CHUNK_SIZE', '128'),

View File

@@ -1,6 +1,5 @@
const defaultState = {
emojis :
{
"emojisList" :
[
{
"codes": "1F600",
@@ -148,7 +147,7 @@ const defaultState = {
},
{
"codes": "263A",
"char": "☺",
"char": "☺",
"name": "smiling face",
"category": "Smileys & Emotion (face-affection)",
"group": "Smileys & Emotion",
@@ -13947,14 +13946,13 @@ const defaultState = {
"subgroup": "subdivision-flag"
}
],
emojiGroups : [
"emojisGroups" : [
{
"name": "Smileys & Emotion",
"emoji": {
"codes": "1F600",
"char": "😀",
"name": "grinning face",
"name": "grinning face"
}
},
{
@@ -13962,7 +13960,7 @@ const defaultState = {
"emoji": {
"codes": "1F91A",
"char": "🤚",
"name": "raised back of hand",
"name": "raised back of hand"
}
},
{
@@ -13970,7 +13968,7 @@ const defaultState = {
"emoji": {
"codes": "1F435",
"char": "🐵",
"name": "monkey face",
"name": "monkey face"
}
},
{
@@ -13978,7 +13976,7 @@ const defaultState = {
"emoji": {
"codes": "1F34F",
"char": "🍏",
"name": "green apple",
"name": "green apple"
}
},
{
@@ -13986,7 +13984,7 @@ const defaultState = {
"emoji": {
"codes": "1F697",
"char": "🚗",
"name": "automobile",
"name": "automobile"
}
},
{
@@ -13994,7 +13992,7 @@ const defaultState = {
"emoji": {
"codes": "26BD",
"char": "⚽",
"name": "soccer ball",
"name": "soccer ball"
}
},
{
@@ -14002,15 +14000,15 @@ const defaultState = {
"emoji": {
"codes": "231A",
"char": "⌚",
"name": "watch",
"name": "watch"
}
},
{
"name": "Symbols",
"emoji": {
"codes": "2764",
"char": "",
"name": "red heart",
"codes": "2705",
"char": "",
"name": "check mark button"
}
},
{
@@ -14018,21 +14016,8 @@ const defaultState = {
"emoji": {
"codes": "1F3F3",
"char": "🏳",
"name": "white flag",
"name": "white flag"
}
},
}
]
}
const getters = {
emojis: state => state.emojis,
emojiGroups: state => state.emojiGroups
}
export default {
state: defaultState,
getters
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/main.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"/chunks/files~chunks/shared-files~chunks/shared-page.js": "/chunks/files~chunks/shared-files~chunks/shared-page.js?id=42d1881aa3cd2b5a0e21",
"/js/main.js": "/js/main.js?id=86eb60282c3151df7fa7",
"/chunks/files~chunks/shared-files~chunks/shared-page.js": "/chunks/files~chunks/shared-files~chunks/shared-page.js?id=74636ea45210d3b31adf",
"/js/main.js": "/js/main.js?id=4c6692bd2b1bf818d332",
"/css/app.css": "/css/app.css?id=dfd52fc997b919cd3686",
"/chunks/admin.js": "/chunks/admin.js?id=7672646537b5813becf0",
"/chunks/admin-account.js": "/chunks/admin-account.js?id=3f5a34aa8341af8d2b4c",
@@ -15,7 +15,7 @@
"/chunks/billings-detail.js": "/chunks/billings-detail.js?id=b73a5b6f7d2a448cc5ab",
"/chunks/contact-us.js": "/chunks/contact-us.js?id=81906d205ba0107c5105",
"/chunks/create-new-password.js": "/chunks/create-new-password.js?id=004908727045abd0852e",
"/chunks/dashboard.js": "/chunks/dashboard.js?id=08e4f7d923ce9e49dcc3",
"/chunks/dashboard.js": "/chunks/dashboard.js?id=cdfd468f0d0f98b9f081",
"/chunks/database.js": "/chunks/database.js?id=b8d8269f77c52f78c784",
"/chunks/dynamic-page.js": "/chunks/dynamic-page.js?id=2e3af103d13536c50757",
"/chunks/environment-setup.js": "/chunks/environment-setup.js?id=106f81cefe76c62d476e",
@@ -44,7 +44,7 @@
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=aa3d963f578d7bc5ff88",
"/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=47090233afc7b0cdf855",
"/chunks/shared-files.js": "/chunks/shared-files.js?id=ba10fd3f52a7b62d3092",
"/chunks/shared-page.js": "/chunks/shared-page.js?id=4489cb4cfa33fb249bea",
"/chunks/shared-page.js": "/chunks/shared-page.js?id=8e0b9c767ed8a703138a",
"/chunks/sign-in.js": "/chunks/sign-in.js?id=c52ce81c3dad56d7a7d8",
"/chunks/sign-up.js": "/chunks/sign-up.js?id=2f12850d320b2413cf54",
"/chunks/stripe-credentials.js": "/chunks/stripe-credentials.js?id=6622381f1d96e8319999",

View File

@@ -110,7 +110,7 @@ export default {
},
computed: {
...mapGetters([
'isLogged', 'isGuest', 'config'
'isLogged', 'isGuest', 'config', 'fileQueue'
]),
isGuestLayout() {
return (includes([

View File

@@ -112,7 +112,7 @@ export default {
'shared',
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
},
canUploadInView() {
return !this.$isThisLocation(['base', 'public'])
@@ -125,7 +125,7 @@ export default {
'shared',
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
},
canShareInView() {
@@ -137,7 +137,7 @@ export default {
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length > 1 || this.fileInfoDetail.length === 0
return !this.$isThisLocation(locations) || this.fileInfoDetail.length > 1 || this.fileInfoDetail.length === 0
}
},
data() {
@@ -183,14 +183,14 @@ export default {
events.$emit('folder:actions', this.currentFolder)
},
deleteItem() {
if(this.fileInfoDetail.length > 0)
if (this.fileInfoDetail.length > 0)
this.$store.dispatch('deleteItem')
},
createFolder() {
this.$store.dispatch('createFolder', this.$t('popup_create_folder.folder_default_name'))
},
moveItem() {
if(this.fileInfoDetail.length > 0)
if (this.fileInfoDetail.length > 0)
events.$emit('popup:open', { name: 'move', item: this.fileInfoDetail })
},
shareItem() {
@@ -215,9 +215,9 @@ export default {
// this.sortingAndPreview = state
// })
events.$on('unClick', () => {
this.sortingAndPreview = false
})
events.$on('unClick', () => {
this.sortingAndPreview = false
})
}
}
</script>
@@ -225,15 +225,18 @@ export default {
<style scoped lang="scss">
@import "@assets/vue-file-manager/_variables";
@import "@assets/vue-file-manager/_mixins";
.preview-sorting {
.preview-sorting {
/deep/ .label {
color: $text !important;
}
/deep/ .preview-sorting {
path, line, polyline, rect, circle {
stroke: $text !important;
}
path, line, polyline, rect, circle {
stroke: $text !important;
}
}
&:hover {
/deep/ .preview-sorting {
path, line, polyline, rect, circle {
@@ -358,6 +361,7 @@ export default {
&.preview-sorting {
background: $light_background;
/deep/ .preview-sorting {
path, line, polyline, rect, circle {
stroke: $theme !important;
@@ -423,14 +427,15 @@ export default {
background: $dark_mode_foreground !important;
}
}
.preview-sorting {
.preview-sorting {
/deep/ .label {
color: $text !important;
}
/deep/ .preview-sorting {
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary !important;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary !important;
}
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="file-content" id="file-content-id" :class="{ 'is-offset': uploadingFilesCount, 'is-dragging': isDragging }"
<div class="file-content" id="file-content-id" :class="{ 'is-offset': filesInQueueTotal > 0, 'is-dragging': isDragging }"
@dragover.prevent
@drop.stop.prevent="dropUpload($event)"
@dragover="dragEnter"
@@ -34,7 +34,7 @@
@dragstart="dragStart(item)"
@drop.stop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:data="item"
:item="item"
v-for="item in data"
:key="item.unique_id"
class="file-item"
@@ -55,7 +55,7 @@
@dragstart="dragStart(item)"
@drop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:data="item"
:item="item"
v-for="item in data"
:key="item.unique_id"
class="file-item"
@@ -119,7 +119,7 @@
},
computed: {
...mapGetters([
'uploadingFilesCount',
'filesInQueueTotal',
'fileInfoVisible',
'fileInfoDetail',
'currentFolder',

View File

@@ -8,52 +8,48 @@
<div class="icon-item">
<!-- MultiSelecting for the mobile version -->
<div :class="{'check-select-folder' : this.data.type === 'folder', 'check-select' : this.data.type !== 'folder'}" v-if="multiSelectMode">
<div :class="{'check-select-folder' : this.item.type === 'folder', 'check-select' : this.item.type !== 'folder'}" v-if="multiSelectMode">
<div class="select-box" :class="{'select-box-active' : isClicked } ">
<CheckIcon v-if="isClicked" class="icon" size="17"/>
</div>
</div>
<!--If is file or image, then link item-->
<span v-if="isFile || (isImage && !data.thumbnail)" class="file-icon-text">
{{ data.mimetype }}
<span v-if="isFile || (isImage && !item.thumbnail)" class="file-icon-text">
{{ item.mimetype }}
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile || (isImage && !data.thumbnail)" class="file-icon" icon="file"/>
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" icon="file"/>
<!--Image thumbnail-->
<img loading="lazy" v-if="isImage && data.thumbnail" class="image" :src="data.thumbnail" :alt="data.name"/>
<!-- If folder have set emoji -->
<Emoji class="emoji" v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="80" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder && !folderIconHandle" :ref="`folder${this.data.unique_id}`" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
<img loading="lazy" v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name"/>
<!--Else show only folder icon-->
<FolderIcon v-if="isFolder" :item="item" location="file-item-grid" class="folder"/>
</div>
<!--Name-->
<div class="item-name">
<!--Name-->
<b :ref="this.data.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
<b :ref="this.item.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
{{ itemName }}
</b>
<div class="item-info">
<!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared">
<div v-if="$checkPermission('master') && item.shared" class="item-shared">
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<div v-if="$checkPermission('master') && item.user_scope !== 'master'" class="item-shared">
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Filesize-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}</span>
<span v-if="! isFolder" class="item-size">{{ item.filesize }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length">
@@ -62,7 +58,7 @@
</div>
</div>
<span @click.stop="showItemActions" class="show-actions" v-if="$isMobile() && ! ( $checkPermission('visitor') && isFolder || multiSelectMode ) && canShowMobileOptions">
<span @click.stop="showItemActions" class="show-actions" v-if="$isMobile() && ! multiSelectMode && canShowMobileOptions">
<FontAwesomeIcon icon="ellipsis-h" class="icon-action"></FontAwesomeIcon>
</span>
</div>
@@ -71,61 +67,60 @@
<script>
import { LinkIcon, UserPlusIcon, CheckIcon } from 'vue-feather-icons'
import Emoji from '@/components/Others/Emoji'
import FolderIcon from '@/components/FilesView/FolderIcon'
import { debounce } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: 'FileItemGrid',
props: ['data'],
props: ['item'],
components: {
UserPlusIcon,
CheckIcon,
LinkIcon,
Emoji
FolderIcon,
},
computed: {
...mapGetters([
'FilePreviewType', 'sharedDetail', 'fileInfoDetail'
'FilePreviewType', 'sharedDetail', 'fileInfoDetail', 'data'
]),
folderIconHandle(){
folderEmojiOrColor(){
// If folder have set some color
if(this.data.icon_color) {
if(this.item.icon_color) {
this.$nextTick(() => {
this.$refs[`folder${this.data.unique_id}`].firstElementChild.style.fill = `${this.data.icon_color}`
this.$refs[`folder${this.item.unique_id}`].firstElementChild.style.fill = `${this.item.icon_color}`
})
return false
}
// If folder have set some emoji
if(this.data.icon_emoji)
return this.data.icon_emoji
if(this.item.icon_emoji)
return this.item.icon_emoji
},
...mapGetters({ allData: 'data' }),
isClicked() {
return this.fileInfoDetail.some(element => element.unique_id == this.data.unique_id)
return this.fileInfoDetail.some(element => element.unique_id == this.item.unique_id)
},
isFolder() {
return this.data.type === 'folder'
return this.item.type === 'folder'
},
isFile() {
return this.data.type !== 'folder' && this.data.type !== 'image'
return this.item.type !== 'folder' && this.item.type !== 'image'
},
isPdf() {
return this.data.mimetype === 'pdf'
return this.item.mimetype === 'pdf'
},
isImage() {
return this.data.type === 'image'
return this.item.type === 'image'
},
isVideo() {
return this.data.type === 'video'
return this.item.type === 'video'
},
isAudio() {
let mimetypes = ['mpeg', 'mp3', 'mp4', 'wan', 'flac']
return mimetypes.includes(this.data.mimetype) && this.data.type === 'audio'
return mimetypes.includes(this.item.mimetype) && this.item.type === 'audio'
},
canEditName() {
return !this.$isMobile()
@@ -140,13 +135,13 @@ export default {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
timeStamp() {
return this.data.deleted_at ? this.$t('item_thumbnail.deleted_at', this.data.deleted_at) : this.data.created_at
return this.item.deleted_at ? this.$t('item_thumbnail.deleted_at', this.item.deleted_at) : this.item.created_at
},
folderItems() {
return this.data.deleted_at ? this.data.trashed_items : this.data.items
return this.item.deleted_at ? this.item.trashed_items : this.item.items
},
isDeleted() {
return this.data.deleted_at ? true : false
return this.item.deleted_at ? true : false
}
},
data() {
@@ -163,12 +158,12 @@ export default {
showItemActions() {
// Load file info detail
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('mobileMenu:show')
},
dragEnter() {
if (this.data.type !== 'folder') return
if (this.item.type !== 'folder') return
this.area = true
},
@@ -185,15 +180,15 @@ export default {
if (e.ctrlKey || e.metaKey && !e.shiftKey) {
// Click + Ctrl
if (this.fileInfoDetail.some(item => item.unique_id === this.data.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.data)
if (this.fileInfoDetail.some(item => item.unique_id === this.item.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
} else if (e.shiftKey) {
// Click + Shift
let lastItem = this.allData.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.allData.indexOf(this.data)
let lastItem = this.data.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.data.indexOf(this.item)
// If Click + Shift + Ctrl dont remove already selected items
if (!e.ctrlKey && !e.metaKey) {
@@ -203,18 +198,18 @@ export default {
//Shift selecting from top to bottom
if (lastItem < clickedItem) {
for (let i = lastItem; i <= clickedItem; i++) {
this.$store.commit('GET_FILEINFO_DETAIL', this.allData[i])
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
}
//Shift selecting from bottom to top
} else {
for (let i = lastItem; i >= clickedItem; i--) {
this.$store.commit('GET_FILEINFO_DETAIL', this.allData[i])
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
}
}
} else {
// Click
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
}
@@ -223,29 +218,25 @@ export default {
if (this.$isMobile() && this.isFolder) {
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
} else {
this.$store.dispatch('getFolder', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
}
}
if (this.$isMobile()) {
if (this.isImage || this.isVideo || this.isAudio) {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('fileFullPreview:show')
}else {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
}
}
}
if (this.multiSelectMode && this.$isMobile()) {
if (this.fileInfoDetail.some(item => item.unique_id === this.data.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.data)
if (this.fileInfoDetail.some(item => item.unique_id === this.item.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
}
// Get target classname
@@ -263,7 +254,7 @@ export default {
events.$emit('fileFullPreview:show')
} else if (this.isFile || !this.isFolder && !this.isPdf && !this.isVideo && !this.isAudio && !this.isImage) {
this.$downloadFile(this.data.file_url, this.data.name + '.' + this.data.mimetype)
this.$downloadFile(this.item.file_url, this.item.name + '.' + this.item.mimetype)
} else if (this.isFolder) {
@@ -271,9 +262,9 @@ export default {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
} else {
this.$store.dispatch('getFolder', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
}
}
},
@@ -283,18 +274,18 @@ export default {
if (e.target.innerText.trim() === '') return
this.$store.dispatch('renameItem', {
unique_id: this.data.unique_id,
type: this.data.type,
unique_id: this.item.unique_id,
type: this.item.type,
name: e.target.innerText
})
}, 300)
},
created() {
this.itemName = this.data.name
this.itemName = this.item.name
events.$on('newFolder:focus', (unique_id) => {
if(this.data.unique_id == unique_id) {
if(this.item.unique_id == unique_id && !this.$isMobile()) {
this.$refs[unique_id].focus()
document.execCommand('selectAll')
}
@@ -311,7 +302,7 @@ export default {
})
// Change item name
events.$on('change:name', (item) => {
if (this.data.unique_id == item.unique_id) this.itemName = item.name
if (this.item.unique_id == item.unique_id) this.itemName = item.name
})
}
}
@@ -485,9 +476,6 @@ export default {
display: flex;
align-items: center;
.emoji {
margin: 0 auto;
}
.file-link {
display: block;
@@ -531,19 +519,13 @@ export default {
pointer-events: none;
}
.folder-icon {
align-items: flex-end;
@include font-size(80);
margin: 0 auto;
.folder {
width: 80px;
height: 80px;
margin: auto;
path {
fill: $theme;
}
&.is-deleted {
path {
fill: $dark_background;
}
/deep/ .folder-icon {
@include font-size(80)
}
}
}
@@ -577,11 +559,17 @@ export default {
.file-icon-text {
@include font-size(12);
}
.folder-icon {
@include font-size(75);
.folder {
width: 75px;
height: 75px;
margin-top: 0;
margin-bottom: 0;
/deep/ .folder-icon {
@include font-size(75)
}
}
.image {
@@ -623,15 +611,6 @@ export default {
stroke: #2F3C54;
}
}
.folder-icon {
&.is-deleted {
path {
fill: lighten($dark_mode_foreground, 5%);
}
}
}
}
.file-item {

View File

@@ -21,43 +21,39 @@
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile || (isImage && !data.thumbnail)" class="file-icon-text">
{{ data.mimetype | limitCharacters }}
<span v-if="isFile || (isImage && !item.thumbnail)" class="file-icon-text">
{{ item.mimetype | limitCharacters }}
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile || (isImage && !data.thumbnail)" class="file-icon" icon="file"/>
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" icon="file"/>
<!--Image thumbnail-->
<img loading="lazy" v-if="isImage && data.thumbnail" class="image" :src="data.thumbnail" :alt="data.name"/>
<!-- If folder have set emoji -->
<Emoji v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="52" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder && !folderIconHandle" :ref="`folder${this.data.unique_id}`" :class="{ 'is-deleted': isDeleted }" class="folder-icon" icon="folder"/>
<img loading="lazy" v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name"/>
<!--Else show only folder icon-->
<FolderIcon v-if="isFolder" :item="item" location="file-item-list" class="folder" />
</div>
<!--Name-->
<div class="item-name">
<b :ref="this.data.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
<b :ref="this.item.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
{{ itemName }}
</b>
<div class="item-info">
<!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared">
<div v-if="$checkPermission('master') && item.shared" class="item-shared">
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<div v-if="$checkPermission('master') && item.user_scope !== 'master'" class="item-shared">
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Filesize and timestamp-->
<span v-if="!isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<span v-if="!isFolder" class="item-size">{{ item.filesize }}, {{ timeStamp }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length"> {{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }} </span>
@@ -66,7 +62,7 @@
<!--Show item actions-->
<transition name="slide-from-right">
<div class="actions" v-if="$isMobile() && !($checkPermission('visitor') && isFolder || mobileMultiSelect)">
<div class="actions" v-if="$isMobile() && ! mobileMultiSelect">
<span @click.stop="showItemActions" class="show-actions">
<FontAwesomeIcon icon="ellipsis-v" class="icon-action"></FontAwesomeIcon>
</span>
@@ -78,59 +74,43 @@
<script>
import { LinkIcon, UserPlusIcon, CheckIcon } from 'vue-feather-icons'
import Emoji from '@/components/Others/Emoji'
import FolderIcon from '@/components/FilesView/FolderIcon'
import { debounce } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: 'FileItemList',
props: ['data'],
props: ['item'],
components: {
UserPlusIcon,
LinkIcon,
FolderIcon,
CheckIcon,
Emoji
},
computed: {
...mapGetters(['FilePreviewType', 'fileInfoDetail']),
...mapGetters({ allData: 'data' }),
folderIconHandle(){
// If folder have set some icon color
if(this.data.icon_color) {
this.$nextTick(() => {
this.$refs[`folder${this.data.unique_id}`].firstElementChild.style.fill = `${this.data.icon_color}`
})
return false
}
// If folder have set some emoji
if(this.data.icon_emoji)
return this.data.icon_emoji
},
...mapGetters(['FilePreviewType', 'fileInfoDetail', 'data']),
isClicked() {
return this.fileInfoDetail.some(element => element.unique_id == this.data.unique_id)
return this.fileInfoDetail.some(element => element.unique_id == this.item.unique_id)
},
isFolder() {
return this.data.type === 'folder'
return this.item.type === 'folder'
},
isFile() {
return this.data.type !== 'folder' && this.data.type !== 'image'
return this.item.type !== 'folder' && this.item.type !== 'image'
},
isImage() {
return this.data.type === 'image'
return this.item.type === 'image'
},
isPdf() {
return this.data.mimetype === 'pdf'
return this.item.mimetype === 'pdf'
},
isVideo() {
return this.data.type === 'video'
return this.item.type === 'video'
},
isAudio() {
let mimetypes = ['mpeg', 'mp3', 'mp4', 'wan', 'flac']
return mimetypes.includes(this.data.mimetype) && this.data.type === 'audio'
return mimetypes.includes(this.item.mimetype) && this.item.type === 'audio'
},
canEditName() {
return !this.$isMobile() && !this.$isThisLocation(['trash', 'trash-root']) && !this.$checkPermission('visitor') && !(this.sharedDetail && this.sharedDetail.type === 'file')
@@ -139,13 +119,13 @@ export default {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
timeStamp() {
return this.data.deleted_at ? this.$t('item_thumbnail.deleted_at', { time: this.data.deleted_at }) : this.data.created_at
return this.item.deleted_at ? this.$t('item_thumbnail.deleted_at', { time: this.item.deleted_at }) : this.item.created_at
},
folderItems() {
return this.data.deleted_at ? this.data.trashed_items : this.data.items
return this.item.deleted_at ? this.item.trashed_items : this.item.items
},
isDeleted() {
return this.data.deleted_at ? true : false
return this.item.deleted_at ? true : false
}
},
filters: {
@@ -172,12 +152,12 @@ export default {
showItemActions() {
// Load file info detail
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('mobileMenu:show')
},
dragEnter() {
if (this.data.type !== 'folder') return
if (this.item.type !== 'folder') return
this.area = true
},
@@ -195,15 +175,15 @@ export default {
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
// Click + Ctrl
if (this.fileInfoDetail.some(item => item.unique_id === this.data.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.data)
if (this.fileInfoDetail.some(item => item.unique_id === this.item.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
} else if (e.shiftKey) {
// Click + Shift
let lastItem = this.allData.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.allData.indexOf(this.data)
let lastItem = this.data.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.data.indexOf(this.item)
// If Click + Shift + Ctrl dont remove already selected items
if (!e.ctrlKey && !e.metaKey) {
@@ -213,18 +193,18 @@ export default {
//Shift selecting from top to bottom
if (lastItem < clickedItem) {
for (let i = lastItem; i <= clickedItem; i++) {
this.$store.commit('GET_FILEINFO_DETAIL', this.allData[i])
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
}
//Shift selecting from bottom to top
} else {
for (let i = lastItem; i >= clickedItem; i--) {
this.$store.commit('GET_FILEINFO_DETAIL', this.allData[i])
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
}
}
} else {
// Click
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
}
@@ -233,29 +213,25 @@ export default {
if (this.$isMobile() && this.isFolder) {
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
} else {
this.$store.dispatch('getFolder', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
}
}
if (this.$isMobile()) {
if (this.isImage || this.isVideo || this.isAudio) {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('fileFullPreview:show')
} else {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
}
}
}
if (this.mobileMultiSelect && this.$isMobile()) {
if (this.fileInfoDetail.some(item => item.unique_id === this.data.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.data)
if (this.fileInfoDetail.some(item => item.unique_id === this.item.unique_id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
}
}
@@ -269,7 +245,7 @@ export default {
events.$emit('fileFullPreview:show')
} else if (this.isFile || !this.isFolder && !this.isPdf && !this.isVideo && !this.isAudio && !this.isImage) {
this.$downloadFile(this.data.file_url, this.data.name + '.' + this.data.mimetype)
this.$downloadFile(this.item.file_url, this.item.name + '.' + this.item.mimetype)
} else if (this.isFolder) {
@@ -277,9 +253,9 @@ export default {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
} else {
this.$store.dispatch('getFolder', [{ folder: this.data, back: false, init: false }])
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
}
}
},
@@ -288,19 +264,19 @@ export default {
if (e.target.innerText.trim() === '') return
this.$store.dispatch('renameItem', {
unique_id: this.data.unique_id,
type: this.data.type,
unique_id: this.item.unique_id,
type: this.item.type,
name: e.target.innerText
})
}, 300)
},
created() {
this.itemName = this.data.name
this.itemName = this.item.name
events.$on('newFolder:focus', (unique_id) => {
if(this.data.unique_id == unique_id) {
if(this.item.unique_id == unique_id && !this.$isMobile()) {
this.$refs[unique_id].focus()
document.execCommand('selectAll')
}
@@ -318,7 +294,7 @@ export default {
// Change item name
events.$on('change:name', (item) => {
if (this.data.unique_id == item.unique_id) this.itemName = item.name
if (this.item.unique_id == item.unique_id) this.itemName = item.name
})
}
}
@@ -480,18 +456,13 @@ export default {
flex: 0 0 50px;
line-height: 0;
margin-right: 20px;
.folder {
width: 52px;
height: 52px;
.folder-icon {
@include font-size(52);
path {
fill: $theme;
}
&.is-deleted {
path {
fill: $dark_background;
}
/deep/ .folder-icon {
@include font-size(52)
}
}
@@ -589,14 +560,6 @@ export default {
stroke: #2f3c54;
}
}
.folder-icon {
&.is-deleted {
path {
fill: lighten($dark_mode_foreground, 5%);
}
}
}
}
.file-item {

View File

@@ -0,0 +1,116 @@
<template>
<div :class="[{'is-apple': $isApple()}, location]">
<Emoji
v-if="emoji"
:emoji="emoji"
class="emoji-icon"
/>
<FontAwesomeIcon
v-if="!emoji"
:class="[{ 'is-deleted': isDeleted },{'default-color' : ! color && ! isDeleted}, 'folder-icon' ]"
:style="{fill: color}"
icon="folder"
/>
</div>
</template>
<script>
import Emoji from '@/components/Others/Emoji'
export default {
name: 'FolderIcon',
props: [
'item',
'folderIcon',
'location'
],
components: {
Emoji
},
computed: {
isDeleted() {
return this.item.deleted_at ? true : false
},
emoji() {
// Return emoji if is changed from rename popup
if (this.folderIcon)
return this.folderIcon.emoji ? this.folderIcon.emoji : false
// Return emoji if is already set
return this.item.icon_emoji ? this.item.icon_emoji : false
},
color() {
// Return color if is changed from rename popup
if (this.folderIcon)
return this.folderIcon.color ? this.folderIcon.color : false
// Return color if is already set
return this.item.icon_color ? this.item.icon_color : false
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
// Locations
.file-item-list {
&.is-apple .emoji-icon {
font-size: 50px;
line-height: 1.1;
}
}
.file-item-grid {
&.is-apple .emoji-icon {
font-size: 80px;
line-height: 1.1;
}
}
.thumbnail-item {
&.is-apple .emoji-icon {
font-size: 36px;
line-height: 1.1;
}
}
.emoji-picker-preview {
&.is-apple .emoji-icon {
font-size: 22px;
line-height: 1.1;
}
}
.default-color {
path {
fill: $theme !important;
}
}
.folder-icon {
path {
fill: inherit;
}
&.is-deleted {
path {
fill: $dark_background;
}
}
}
@media (prefers-color-scheme: dark) {
.folder-icon {
&.is-deleted {
path {
fill: lighten($dark_mode_foreground, 5%);
}
}
}
}
</style>

View File

@@ -29,8 +29,8 @@
</li>
</ul>
<ul class="menu-option-group" v-if="!isFolder">
<li class="menu-option" @click="downloadItem">
<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>
@@ -38,6 +38,15 @@
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
@@ -98,6 +107,15 @@
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
@@ -166,6 +184,15 @@
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
@@ -207,6 +234,15 @@
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
@@ -221,6 +257,15 @@
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
</div>
@@ -239,6 +284,7 @@ import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
@@ -256,6 +302,7 @@ export default {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
ThumbnailItem,
LifeBuoyIcon,
Trash2Icon,
@@ -297,6 +344,9 @@ export default {
}
},
methods: {
downloadFolder(){
this.$store.dispatch( 'downloadFolder' , this.fileInfoDetail[0] )
},
moveItem() {
events.$emit('popup:open', { name: 'move', item: [this.fileInfoDetail[0]] })
},

View File

@@ -1,20 +1,21 @@
<template>
<transition name="info-panel">
<div v-if="uploadingFilesCount" class="upload-progress">
<div v-if="fileQueue.length > 0" class="upload-progress">
<div class="progress-title">
<!--Is processing-->
<span v-if="isProcessingFile">
<refresh-cw-icon size="12" class="sync-alt"></refresh-cw-icon>
{{ $t('uploading.processing_file') }}
</span>
<span v-if="!isProcessingFile && uploadingFilesCount.total === 1">
{{ $t('uploading.progress_single_upload', {progress: uploadingFileProgress}) }}
</span>
<span v-if="!isProcessingFile && uploadingFilesCount.total > 1">
{{ $t('uploading.progress', {current:uploadingFilesCount.current, total: uploadingFilesCount.total, progress: uploadingFileProgress}) }}
<!--Multi file upload-->
<span v-if="!isProcessingFile && fileQueue.length > 0">
{{ $t('uploading.progress', {current:filesInQueueUploaded, total: filesInQueueTotal, progress: uploadingProgress}) }}
</span>
</div>
<div class="progress-wrapper">
<ProgressBar :progress="uploadingFileProgress" />
<ProgressBar :progress="uploadingProgress" />
<span @click="cancelUpload" :title="$t('uploading.cancel')" class="cancel-icon">
<x-icon size="16" @click="cancelUpload"></x-icon>
</span>
@@ -38,9 +39,11 @@
},
computed: {
...mapGetters([
'uploadingFileProgress',
'uploadingFilesCount',
'filesInQueueUploaded',
'filesInQueueTotal',
'uploadingProgress',
'isProcessingFile',
'fileQueue',
])
},
methods: {

View File

@@ -79,16 +79,16 @@
this.$store.dispatch('createFolder', this.name)
this.$closePopup()
this.name = undefined
}
},
},
mounted() {
events.$on('popup:open', ({name}) => {
if (name === 'create-folder')
this.$nextTick(() => {
this.$refs.input.focus()
})
if (name === 'create-folder' && ! this.$isMobile())
this.$nextTick(() => this.$refs.input.focus())
})
}
}

View File

@@ -1,25 +1,69 @@
<template>
<div v-show="transferEmoji" :style="{width: `${size}px`, height: `${size}px`}" v-html="transferEmoji"/>
<div :class="[location, 'emoji-container', {'is-apple': $isApple}]">
<span v-if="!$isApple()" class="twemoji-emoji emoji-icon" v-html="transferEmoji"></span>
<span v-if="$isApple()" class="apple-emoji emoji-icon">{{ this.emoji.char }}</span>
</div>
</template>
<script>
import twemoji from 'twemoji'
export default {
name: 'Emoji',
props: ['emoji', 'size'],
computed: {
transferEmoji () {
// Transfer single emoji to twemoji
return twemoji.parse(this.emoji.char, {
folder: 'svg',
ext: '.svg',
attributes: () => ({
loading: 'lazy',
})
export default {
name: 'Emoji',
props: [
'emoji',
'location',
],
computed: {
transferEmoji() {
return twemoji.parse(this.emoji.char, {
folder: 'svg',
ext: '.svg',
attributes: () => ({
loading: 'lazy'
})
}
},
}
})
}
},
}
</script>
<style lang="scss" scoped>
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.emoji-container {
font-size: inherit;
.emoji-icon {
font-size: inherit;
}
}
.emoji-picker {
.apple-emoji {
font-size: 34px;
line-height: 1.1;
font-family: "Apple Color Emoji";
}
}
.emoji-picker-preview {
.apple-emoji {
font-size: 28px;
line-height: 0.85;
font-family: "Apple Color Emoji";
}
}
@media only screen and (max-width: 690px) {
.groups-list .emoji-picker {
.apple-emoji {
font-size: 34px;
line-height: 1.1;
}
}
}
</style>

View File

@@ -24,9 +24,9 @@
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--<SetFolderIcon v-if="isMoreOptions" :folderData="pickedItem" :unique_id="pickedItem.unique_id" />-->
<SetFolderIcon v-if="isMoreOptions" :folderData="pickedItem" :unique_id="pickedItem.unique_id" />
<!-- <ActionButton v-if="pickedItem.type === 'folder'" @click.native.stop="moreOptions" :icon="isMoreOptions ? 'x' : 'pencil-alt'">{{ moreOptionsTitle }}</ActionButton> -->
<ActionButton v-if="pickedItem.type === 'folder'" @click.native.stop="moreOptions" :icon="isMoreOptions ? 'x' : 'pencil-alt'">{{ moreOptionsTitle }}</ActionButton>
</ValidationObserver>
@@ -92,8 +92,6 @@ export default {
methods: {
moreOptions() {
this.isMoreOptions = !this.isMoreOptions
this.setFolderIcon = undefined
},
changeName() {
if (this.pickedItem.name && this.pickedItem.name !== '') {
@@ -122,9 +120,9 @@ export default {
if (args.name !== 'rename-item') return
this.$nextTick(() => {
this.$refs.input.focus()
})
if (! this.$isMobile()) {
this.$nextTick(() => this.$refs.input.focus())
}
this.isMoreOptions = false
@@ -135,7 +133,7 @@ export default {
})
events.$on('setFolderIcon', (icon) => {
this.setFolderIcon = !icon ? undefined : icon.value
this.setFolderIcon = icon.value
})
}
}

View File

@@ -1,196 +1,247 @@
<template>
<div class="set-folder-icon">
<TabWrapper class="set-folder-icon">
<TabWrapper >
<!-- Emojis -->
<TabOption :selected="true" id="emoji-list" :title="$t('popup_rename.tab_emoji_title')" icon="emoji">
<div class="select-emoji-wrapper">
<label class="main-label">{{ $t('popup_rename.select_emoji_label') }}:</label>
<!-- Emojis -->
<TabOption :selected="true" id="emoji-list" :title="$t('popup_rename.tab_emoji_title')" icon="emoji">
<div class="select-emoji-wrapper">
<label class="main-label">Pick Yout Emoji Icon:</label>
<!-- Selected Emoji input -->
<div @click.stop="openMenu" class="select-input-wrapper" :class="{'active-menu' : selectOpen}">
<!-- Selected Emoji input -->
<div @click.stop="openMenu" class="select-input-wrapper">
<div class="select-input" v-if="selectedEmoji">
<Emoji class="emoji-preview" :emoji="selectedEmoji" size="25"></Emoji>
<span>{{selectedEmoji.name}}</span>
</div>
<div class="not-selected" v-if="! selectedEmoji">
<span> {{$t('popup_rename.set_emoji_input_placeholder')}}</span>
</div>
<chevron-down-icon v-if="!selectOpen" size="19"/>
<div v-if="selectOpen" @click="resetEmoji" class="select-input-icon-wrapper">
<!-- If is emoji selected -->
<div class="select-input" v-if="selectedEmoji">
<div @click.stop="resetEmoji" class="select-input-icon-wrapper">
<x-icon size="14" class="select-input-icon"/>
</div>
<Emoji class="emoji-preview" :emoji="selectedEmoji" location="emoji-picker-preview" />
<span>{{ selectedEmoji.name }}</span>
</div>
<!-- Emojis List -->
<transition v-if="selectOpen" name="slide-in">
<div class="emoji-wrapper">
<input @click.stop @input="filterEmojis" v-model="searchInput" class="emoji-input" :placeholder="$t('popup_rename.search_emoji_input_placeholder')" >
<!-- If is emoji not selected -->
<div class="not-selected" v-if="! selectedEmoji">
<span> {{ $t('popup_rename.set_emoji_input_placeholder') }}</span>
</div>
<chevron-down-icon class="row-icon" size="19"/>
</div>
<!-- Emojis List -->
<transition name="slide-in">
<div v-if="selectOpen">
<!-- Spinner -->
<div v-if="!loadedList" class="emoji-wrapper">
<Spinner/>
</div>
<!-- List -->
<div v-if="loadedList && emojis" class="emoji-wrapper">
<!-- Search input -->
<input @click.stop @input="filterEmojis" v-model="searchInput" class="emoji-input" :placeholder="$t('popup_rename.search_emoji_input_placeholder')">
<!-- Navigation of Emojis Groups -->
<ul v-show="searchInput.length < 1" class="groups-list">
<li @click.stop="scrollToGroup(group.name)" v-for="(group,i) in emojiGroups" :key="i" class="group-option" :class="{'active' : group.name === groupInView}">
<Emoji :emoji="group.emoji" size="33"/>
<li @click.stop="scrollToGroup(group.name)" v-for="(group,i) in emojis.emojisGroups" :key="i" class="group-option" :class="{'active' : group.name === groupInView}">
<Emoji :emoji="group.emoji" location="emoji-picker" />
</li>
</ul>
<!-- All Emojis -->
<div v-show="searchInput.length < 1" @scroll="checkGroupInView" id="group-box" class="group-wrapper">
<!-- All Emojis -->
<div v-show="searchInput.length < 1" @scroll="checkGroupInView" id="group-box" class="group-wrapper">
<div v-for="(group, name) in allEmoji" :key="name" class="options-wrapper" :id="`group-${name}`">
<label class="group-name-label">{{name}}</label>
<label class="group-name-label">{{ name }}</label>
<ul class="options-list">
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in group" :key="i" class="option">
<Emoji :emoji="emoji" size="33"/>
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in group" :key="i" class="option">
<Emoji :emoji="emoji" location="emoji-picker" />
</li>
</ul>
</div>
</div>
<!-- Searched emojis -->
<!-- Searched emojis -->
<div v-if="searchInput.length > 0" class="group-wrapper">
<div class="options-wrapper">
<ul class="options-list">
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in filteredEmojis" :key="i" class="option" >
<Emoji :emoji="emoji" size="33"/>
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in filteredEmojis" :key="i" class="option">
<Emoji :emoji="emoji" location="emoji-picker" />
</li>
</ul>
<span class="not-found" v-if="filteredEmojis.length === 0"> {{$t('popup_rename.emoji_list_not_found')}}</span>
<span class="not-found" v-if="filteredEmojis.length === 0"> {{ $t('popup_rename.emoji_list_not_found') }}</span>
</div>
</div>
</div>
</div>
</transition>
</div>
</TabOption>
</transition>
</div>
</TabOption>
<!-- Colors -->
<TabOption :title="$t('popup_rename.tab_color_title')" icon="folder">
<div class="color-pick-wrapper">
<label class="main-label">{{$t('popup_rename.color_pick_label')}}</label>
<ul class="color-wrapper">
<li v-for="(color, index) in colors"
:key="index"
@click="setIcon({'color': color})"
class="single-color"
:class="{'active-color': color === selectedColor }"
:style="{background:color}" />
</ul>
</div>
</TabOption>
</TabWrapper>
</div>
<!-- Colors -->
<TabOption :title="$t('popup_rename.tab_color_title')" icon="folder">
<div class="color-pick-wrapper">
<label class="main-label">{{ $t('popup_rename.color_pick_label') }}:</label>
<ul class="color-wrapper">
<li v-for="(color, i) in colors" :key="i" @click="setIcon({'color': color})" class="single-color">
<check-icon v-if="color === selectedColor" class="color-icon" size="22"/>
<span :style="{background:color}" class="color-box"></span>
</li>
</ul>
</div>
</TabOption>
</TabWrapper>
</template>
<script>
import { SmileIcon, FolderIcon, ChevronDownIcon, XIcon } from 'vue-feather-icons'
import { SmileIcon, FolderIcon, ChevronDownIcon, XIcon, CheckIcon } from 'vue-feather-icons'
import TabWrapper from '@/components/Others/TabWrapper'
import TabOption from '@/components/Others/TabOption'
import Spinner from '@/components/FilesView/Spinner'
import Emoji from '@/components/Others/Emoji'
import lodash from 'lodash'
import { groupBy } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: "SetFolderIcon",
name: 'SetFolderIcon',
props: ['folderData', 'unique_id'],
components: {
ChevronDownIcon ,
ChevronDownIcon,
TabWrapper,
TabOption,
FolderIcon,
SmileIcon,
CheckIcon,
TabOption,
Spinner,
XIcon,
Emoji
},
computed: {
...mapGetters(['emojis', 'emojiGroups']),
...mapGetters(['emojis']),
allEmoji() {
return _.groupBy(this.emojis, 'group')
},
return groupBy(this.emojis.emojisList, 'group')
}
},
data () {
data() {
return {
selectedEmoji: undefined,
selectedColor: undefined,
searchInput: '',
filteredEmojis: [],
selectOpen: false,
loadedList: false,
groupInView: 'Smileys & Emotion',
colors: [ '#FF6633', '#FFB399', '#FF33FF', '#FFFF99', '#00B3E6',
'#E6B333', '#3366E6', '#999966', '#99FF99', '#B34D4D',
'#80B300', '#809900', '#E6B3B3', '#6680B3' ]
colors: [
'#41B883',
'#FE6F6F',
'#FE6F91',
'#FE6FC0',
'#FE6FF0',
'#DD6FFE',
'#AD6FFE',
'#7D6FFE',
'#6F90FE',
'#6FC0FE',
'#6FF0FE',
'#6FFEDD',
'#6FFEAD',
'#6FFE7D',
'#90FE6F',
'#C0FE6F',
'#F0FE6F',
'#FEDD6F',
'#FEAD6F',
'#FE7D6F',
'#4c4c4c',
'#06070B',
]
}
},
methods: {
checkGroupInView: _.debounce(function() {
this.emojiGroups.forEach(group => {
this.emojis.emojisGroups.forEach(group => {
let element = document.getElementById(`group-${group.name}`).getBoundingClientRect()
let groupBox = document.getElementById('group-box').getBoundingClientRect()
// Check if the group is in the viewport of group-box
if(element.top < groupBox.top && element.bottom > groupBox.top){
if (element.top < groupBox.top && element.bottom > groupBox.top) {
this.groupInView = group.name
}
})
}, 200),
scrollToGroup( name ) {
scrollToGroup(name) {
let group = document.getElementById(`group-${name}`)
group.scrollIntoView({ behavior: "smooth" })
group.scrollIntoView({ behavior: 'smooth' })
this.groupInView = name
},
filterEmojis: _.debounce(function( emoji ){
filterEmojis: _.debounce(function(emoji) {
this.filteredEmojis = this.emojis.filter(emoji => emoji.name.includes(this.searchInput))
this.filteredEmojis = this.emojis.emojisList.filter(emoji => emoji.name.includes(this.searchInput))
}, 800),
openMenu() {
this.selectOpen = ! this.selectOpen
this.selectOpen = !this.selectOpen
//Load emojis
if (this.selectOpen) {
this.$store.dispatch('getEmojisList').then((loaded) => {
this.loadedList = loaded
})
}
if (!this.selectOpen)
this.loadedList = false
this.searchInput = ''
this.groupInView = 'Smileys & Emotion'
},
setIcon( value ) {
setIcon(value) {
if(value.emoji){
// Set emoji
if (value.emoji) {
this.selectedEmoji = value.emoji
this.selectedColor = undefined
}
if(value.color) {
// Set color
if (value.color) {
this.selectedColor = value.color
this.selectedEmoji = undefined
}
events.$emit('setFolderIcon', { 'value':value, 'unique_id':this.unique_id })
events.$emit('setFolderIcon', { 'value': value })
this.selectOpen = false
},
resetEmoji(){
resetEmoji() {
this.selectedEmoji = undefined
events.$emit('setFolderIcon', undefined)
events.$emit('setFolderIcon', { 'value': 'default' })
}
},
mounted () {
mounted() {
this.selectOpen = false
// If folder have already set some emoji set this emoji to selected emoji
this.folderData.icon_emoji ? this.selectedEmoji = this.folderData.icon_emoji : ''
// If folder have already set some color set this color to selected color
this.folderData.icon_color ? this.selectedColor = this.folderData.icon_color : ''
events.$on('unClick', () => {
this.selectOpen = false
this.loadedList = false
})
}
}
@@ -202,30 +253,42 @@ export default {
.color-pick-wrapper {
.color-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 20px;
display: grid;
grid-template-columns: repeat(auto-fill, 32px);
justify-content: space-between;
gap: 7px;
.single-color {
width: 40px;
height: 40px;
height: 31px;
list-style: none;
margin: 8px;
border-radius: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
&.active-color {
border: 2px solid $text;
.color-icon {
z-index: 2;
polyline {
stroke: white;
}
}
&:hover {
border: 2px solid $text;
.color-box {
width: 100%;
height: 100%;
position: absolute;
display: block;
}
}
}
}
.select-emoji-wrapper{
.select-emoji-wrapper {
margin-bottom: 20px;
}
@@ -237,7 +300,7 @@ export default {
}
.emoji-wrapper {
height: 350px;
height: 400px;
width: 100%;
position: absolute;
border: 1px solid transparent;
@@ -247,38 +310,36 @@ export default {
display: flex;
flex-direction: column;
padding: 10px;
z-index: 10;
top: 152px;
.loader {
width: 100%;
height: 100%;
position: relative;
}
.groups-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 20px;
display: grid;
grid-template-columns: repeat(9, auto);
justify-content: space-between;
overflow-x: auto;
overflow-y: hidden;
height: 90px;
.active {
.active {
background: $light_background;
border-radius: 8px;
}
.group-option {
width: 45px;
height: 45px;
list-style: none;
padding: 6px;
cursor: pointer;
&:hover {
background: $light_background;
border-radius: 8px;
}
.group-option {
list-style: none;
width: 45px;
height: 45px;
padding: 6px;
cursor: pointer;
&:hover {
background: $light_background;
border-radius: 8px;
}
}
}
}
.emoji-input {
width: 100%;
@@ -311,14 +372,15 @@ export default {
&:last-child {
margin-bottom: 0px;
}
}
.options-list {
display: grid;
grid-template-columns: repeat(auto-fill, 45px);
justify-content: space-between;
width: 100%;
}
.options-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.group-name-label {
width: 100%;
@include font-size(14);
@@ -326,7 +388,7 @@ export default {
margin-bottom: 10px;
}
.option {
.option {
list-style: none;
width: 45px;
height: 45px;
@@ -339,18 +401,16 @@ export default {
}
}
.not-found {
.not-found {
align-self: center;
margin:auto;
margin: auto;
font-weight: 700;
padding: 10px;
border-radius: 8px;
background:$light_background ;
background: $light_background;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
}
}
}
}
@@ -364,28 +424,19 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid transparent;
@include transition(150ms);
.select-input-icon-wrapper {
width: 22px;
height: 22px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
&:hover {
background: $light_background !important;
.row-icon {
@include transition(150ms);
}
.select-input-icon {
line {
stroke: $theme;
}
}
}
&.active-menu {
border-color: $theme;
box-shadow: 0 0 7px rgba($theme, 0.3);
.select-input-icon {
line {
stroke: $text;
}
.row-icon {
transform: rotate(180deg);
}
}
@@ -397,7 +448,35 @@ export default {
align-items: center;
.emoji-preview {
margin-left: 5px;
margin-right: 10px;
width: 22px;
height: 22px;
}
.select-input-icon-wrapper {
width: 22px;
height: 22px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
margin-left: -7px;
&:hover {
.select-input-icon {
line {
stroke: $theme;
}
}
}
.select-input-icon {
line {
stroke: $text;
}
}
}
}
@@ -410,60 +489,76 @@ export default {
}
}
.wrapper {
margin-bottom: 10px;
}
.set-folder-icon {
position: relative;
}
.slide-in-enter-active {
transition: all 5s ease;
.slide-in-enter-active {
transition: all 150ms ease;
}
.slide-in-enter
{
.slide-in-enter {
opacity: 0;
transform: translateY(-50px);
transform: translateY(-210px);
}
.slide-in-enter-to {
transform: translateY(-134px);
}
@media (max-width: 690px) {
.emoji-wrapper {
height: 300px;
}
}
@media (max-width: 336px) {
.emoji-wrapper {
top: 173px;
}
}
@media (prefers-color-scheme: dark) {
.color-pick-wrapper{
.color-wrapper{
.color-pick-wrapper {
.color-wrapper {
.single-color {
&.active-color {
border: 2px solid ;
border: 2px solid;
}
&:hover {
border: 2px solid $dark_mode_text_primary;
}
}
}
}
}
.emoji-wrapper {
background: $dark_mode_background;
.emoji-input {
background: $dark_mode_foreground ;
background: $dark_mode_foreground;
}
.groups-list{
.active{
.groups-list {
.active {
background: $dark_mode_foreground !important;
}
.group-option {
&:hover {
&:hover {
background: $dark_mode_foreground !important;
}
}
}
.options-wrapper {
.option {
&:hover {
background: $dark_mode_foreground !important;
}
}
.not-found {
background: $dark_mode_foreground !important;
}
@@ -472,25 +567,27 @@ export default {
.select-input-wrapper {
background: $dark_mode_foreground;
.not-selected {
span {
color:$dark_mode_text_secondary;
color: $dark_mode_text_secondary;
}
}
.select-input-icon-wrapper {
&:hover {
background: rgba($theme, 0.1) !important;
.select-input-icon {
line {
stroke: $theme !important;
}
}
}
.select-input-icon {
line {
stroke:$dark_mode_text_primary !important;
stroke: $dark_mode_text_primary !important;
}
}
}
}
}
}

View File

@@ -89,11 +89,12 @@
.tab-icon {
margin-right: 10px;
path,
circle,
line,
polyline {
color: $theme !important;
stroke: $theme !important;
}
}
}

View File

@@ -5,20 +5,16 @@
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile || (isImage && !item.thumbnail)" class="file-icon-text">{{ item.mimetype }}</span>
<span v-if="isFile || (isImage && !item.thumbnail) " class="file-icon-text">{{ item.mimetype }}</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" icon="file"/>
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" :class="{'file-icon-mobile' : $isMobile()}" icon="file"/>
<!--Image thumbnail-->
<img v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name"/>
<!-- If folder have set emoji -->
<Emoji v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="36"/>
<!--Else show only folder icon-->
<FontAwesomeIcon ref="folderIcon" v-if="isFolder && !folderIconHandle" class="folder-icon" icon="folder"/>
<FolderIcon v-if="isFolder" :item="item" :folder-icon="setFolderIcon" location="thumbnail-item" class="folder" />
</div>
<!--Name-->
@@ -45,37 +41,14 @@
<script>
import {mapGetters} from 'vuex'
import Emoji from '@/components/Others/Emoji'
import FolderIcon from '@/components/FilesView/FolderIcon'
export default {
name: 'ThumbnailItem',
props: ['item', 'info', 'setFolderIcon'],
components: {Emoji},
components: {FolderIcon},
computed: {
...mapGetters(['currentFolder']),
folderIconHandle(){
// Set icon folder if set folder from rename popup
if(this.setFolderIcon){
return this.setFolderIcon.emoji
? this.setFolderIcon.emoji
: this.$nextTick(() => {
this.$refs.folderIcon.firstElementChild.style.fill = `${this.setFolderIcon.color}`
})
}
// If folder have already set some icon
if(!this.setFolderIcon && (this.item.icon_emoji || this.item.icon_color)){
return this.item.icon_emoji
? this.item.icon_emoji
: this.$nextTick(() => {
this.$refs.folderIcon.firstElementChild.style.fill = `${this.item.icon_color}`
})
}
},
isFolder() {
return this.item.type === 'folder'
},
@@ -137,7 +110,6 @@
justify-content: center;
line-height: 0;
.file-icon {
@include font-size(35);
@@ -148,11 +120,12 @@
}
}
.folder-icon {
@include font-size(36);
.folder {
width: 36px;
height: 36px;
path {
fill: $theme;
/deep/ .folder-icon {
@include font-size(36);
}
}
@@ -202,6 +175,13 @@
}
}
.icon-item .file-icon-mobile {
path {
fill: $dark_mode_background !important;
// stroke: ;
}
}
.item-name {
.name {
color: $dark_mode_text_primary;

View File

@@ -76,123 +76,106 @@ const Helpers = {
this.$store.dispatch('createFolder', folderName)
}
Vue.prototype.$handleUploading = async function (files, parent_id) {
let fileBuffer = []
// Append the file list to fileBuffer array
Array.prototype.push.apply(fileBuffer, files);
let fileSucceed = 0
// Update files count in progressbar
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileSucceed,
total: files.length
})
// Reset upload progress to 0
store.commit('UPLOADING_FILE_PROGRESS', 0)
// Get parent id
let parentFolder = this.$store.getters.currentFolder ? this.$store.getters.currentFolder.unique_id : 0
let rootFolder = parent_id ? parent_id : parentFolder
// Upload files
do {
let file = fileBuffer.shift(),
chunks = []
// Calculate ceils
let size = this.$store.getters.config.chunkSize,
chunksCeil = Math.ceil(file.size / size);
// Create chunks
for (let i = 0; i < chunksCeil; i++) {
chunks.push(file.slice(
i * size, Math.min(i * size + size, file.size), file.type
));
}
// Set Data
let formData = new FormData(),
uploadedSize = 0,
isNotGeneralError = true,
striped_name = file.name.replace(/[^A-Za-z 0-9 \.,\?""!@#\$%\^&\*\(\)-_=\+;:<>\/\\\|\}\{\[\]`~]*/g, ''),
filename = Array(16).fill(0).map(x => Math.random().toString(36).charAt(2)).join('') + '-' + striped_name + '.part'
do {
let isLast = chunks.length === 1,
chunk = chunks.shift(),
attempts = 0
// Set form data
formData.set('file', chunk, filename);
formData.set('parent_id', rootFolder)
formData.set('is_last', isLast);
// Upload chunks
do {
await store.dispatch('uploadFiles', {
form: formData,
fileSize: file.size,
totalUploadedSize: uploadedSize
}).then(() => {
uploadedSize = uploadedSize + chunk.size
}).catch((error) => {
// Count attempts
attempts++
// Break uploading proccess
if (error.response.status === 500)
isNotGeneralError = false
//Break if mimetype of file is in blacklist
if(error.response.status === 415)
isNotGeneralError = false
// Show Error
if (attempts === 3)
this.$isSomethingWrong()
})
} while (isNotGeneralError && attempts !== 0 && attempts !== 3)
} while (isNotGeneralError && chunks.length !== 0)
fileSucceed++
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileSucceed,
total: files.length
})
} while (fileBuffer.length !== 0)
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
}
Vue.prototype.$uploadFiles = async function (files) {
if (files.length == 0) return
if (files.length == 0) return
if (!this.$checkFileMimetype(files) || !this.$checkUploadLimit(files)) return
this.$handleUploading(files, undefined)
// Push items to file queue
[...files].map(item => {
this.$store.commit('ADD_FILES_TO_QUEUE', {
parent_id: store.getters.currentFolder.unique_id,
file: item,
})
});
// Start uploading if uploading process isn't running
if (this.$store.getters.filesInQueueTotal == 0)
this.$handleUploading(store.getters.fileQueue[0])
// Increase total files in upload bar
this.$store.commit('INCREASE_FILES_IN_QUEUES_TOTAL', files.length)
}
Vue.prototype.$uploadExternalFiles = async function (event, parent_id) {
// Prevent submit empty files
if (event.dataTransfer.items.length == 0) return
if (event.dataTransfer.items.length === 0) return
// Get files
let files = [...event.dataTransfer.items].map(item => item.getAsFile());
// Push items to file queue
[...event.dataTransfer.items].map(item => {
this.$store.commit('ADD_FILES_TO_QUEUE', {
parent_id: parent_id,
file: item.getAsFile(),
})
});
this.$handleUploading(files, parent_id)
// Start uploading if uploading process isn't running
if (this.$store.getters.filesInQueueTotal == 0)
this.$handleUploading(this.$store.getters.fileQueue[0])
// Increase total files in upload bar
this.$store.commit('INCREASE_FILES_IN_QUEUES_TOTAL', [...event.dataTransfer.items].length)
}
Vue.prototype.$handleUploading = async function (item) {
// Create ceil
let size = store.getters.config.chunkSize,
chunksCeil = Math.ceil(item.file.size / size),
chunks = []
// Create chunks
for (let i = 0; i < chunksCeil; i++) {
chunks.push(item.file.slice(
i * size, Math.min(i * size + size, item.file.size), item.file.type
));
}
// Set Data
let formData = new FormData(),
uploadedSize = 0,
isNotGeneralError = true,
striped_name = item.file.name.replace(/[^A-Za-z 0-9 \.,\?""!@#\$%\^&\*\(\)-_=\+;:<>\/\\\|\}\{\[\]`~]*/g, ''),
filename = Array(16).fill(0).map(x => Math.random().toString(36).charAt(2)).join('') + '-' + striped_name + '.part'
do {
let isLast = chunks.length === 1,
chunk = chunks.shift(),
attempts = 0
// Set form data
formData.set('file', chunk, filename);
formData.set('parent_id', item.parent_id)
formData.set('is_last', isLast);
// Upload chunks
do {
await store.dispatch('uploadFiles', {
form: formData,
fileSize: item.file.size,
totalUploadedSize: uploadedSize
}).then(() => {
uploadedSize = uploadedSize + chunk.size
}).catch((error) => {
// Count attempts
attempts++
// Show Error
if (attempts === 3)
this.$isSomethingWrong()
// Break uploading process
if ([500, 415].includes(error.response.status))
isNotGeneralError = false
})
} while (isNotGeneralError && attempts !== 0 && attempts !== 3)
} while (isNotGeneralError && chunks.length !== 0)
}
Vue.prototype.$downloadFile = function (url, filename) {
var anchor = document.createElement('a')
@@ -281,6 +264,7 @@ const Helpers = {
message: i18n.t('popup_error.message')
})
}
Vue.prototype.$checkFileMimetype = function(files) {
let validated = true
let mimetypesBlacklist = store.getters.config.mimetypesBlacklist
@@ -345,6 +329,7 @@ const Helpers = {
// Get data of Navigator tree
this.$store.dispatch('getFolderTree')
}
Vue.prototype.$checkOS = function() {
// Handle styled scrollbar for Windows
if (navigator.userAgent.indexOf('Windows') != -1) {
@@ -352,6 +337,22 @@ const Helpers = {
body.classList.add('windows')
}
}
Vue.prototype.$isApple = function() {
const toMatch = [
/iPhone/i,
/iPad/i,
/iPod/i,
/iOS/i,
/macOS/i,
/Macintosh/i
]
// Check if device is iOS
return toMatch.some(toMatchItem => {
return navigator.userAgent.match(toMatchItem)
})
}
}
}

View File

@@ -584,7 +584,14 @@
"popup_rename": {
"title": "Rename Your {item}",
"label": "Edit Name",
"placeholder": "Type your title"
"placeholder": "Type your title",
"tab_emoji_title": "Emoji as an Icon",
"tab_color_title": "Folder Color",
"select_emoji_label": "Pick Your Emoji Icon",
"color_pick_label": "Pick Your Color",
"set_emoji_input_placeholder": "Emojis List...",
"search_emoji_input_placeholder": "Search your emoji...",
"emoji_list_not_found": "Not Found"
},
"popup_set_card": {
"message": "您的卡将被设置为默认卡,并且在以后的结算中始终会收取费用。",

View File

@@ -583,11 +583,11 @@
"placeholder": "Type your title",
"tab_emoji_title": "Emoji as an Icon",
"tab_color_title": "Folder Color",
"select_emoji_label": "Pick Your Emoji Icon",
"color_pick_label": "Pick Your Color",
"set_emoji_input_placeholder": "Emojis List...",
"search_emoji_input_placeholder": "Search your emoji...",
"emoji_list_not_found": "Not Found",
"color_pick_label": "Pick Your Color:"
"emoji_list_not_found": "Not Found"
},
"popup_create_folder": {
"folder_default_name": "New Folder",

View File

@@ -586,7 +586,14 @@
"popup_rename": {
"title": "Zmeňte názov {item}",
"label": "Zmeniť názov",
"placeholder": "Napíš názov..."
"placeholder": "Napíš názov...",
"tab_emoji_title": "Emodži ako ikona",
"tab_color_title": "Farba priečinka",
"select_emoji_label": "Vyberte emodži ikonu",
"color_pick_label": "Vyberte si farbu",
"set_emoji_input_placeholder": "Emodži zoznam ...",
"search_emoji_input_placeholder": "Vyhľadajte svoje emodži...",
"emoji_list_not_found": "Nenájdené"
},
"popup_set_card": {
"message": "Vaša karta bude nastavená ako predvolená a bude vám z nej vždy stiahnutá čiastka za nasledujúce obdobie fakturácie.",

View File

@@ -625,6 +625,17 @@ const routesIndex = [
},
},
]
const routesOthers = [
{
name: 'NotFound',
path: '*',
component: () =>
import(/* webpackChunkName: "chunks/not-found-shared" */ './views/Shared/NotFoundShared'),
meta: {
requiresAuth: false
},
},
]
const router = new Router({
mode: 'history',
@@ -635,6 +646,7 @@ const router = new Router({
...routesIndex,
...routesAuth,
...routesUser,
...routesOthers,
],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {

View File

@@ -3,7 +3,6 @@ import Vue from 'vue'
import fileFunctions from './modules/fileFunctions'
import fileBrowser from './modules/fileBrowser'
import emojisList from './modules/emojisList'
import userAuth from './modules/userAuth'
import sharing from './modules/sharing'
import app from './modules/app'
@@ -14,7 +13,6 @@ export default new Vuex.Store({
modules: {
fileFunctions,
fileBrowser,
emojisList,
userAuth,
sharing,
app,

View File

@@ -8,6 +8,7 @@ const defaultState = {
authorized: undefined,
homeDirectory: undefined,
requestedPlan: undefined,
emojis: undefined,
sorting: {
sort: localStorage.getItem('sorting') ? JSON.parse(localStorage.getItem('sorting')).sort : 'DESC',
field: localStorage.getItem('sorting') ? JSON.parse(localStorage.getItem('sorting')).field : 'created_at',
@@ -967,6 +968,21 @@ const defaultState = {
]
}
const actions = {
getEmojisList: ({commit}) => {
return new Promise((resolve, reject) => {
axios.get('/api/emojis-list')
.then((response) => {
commit('LOAD_EMOJIS_LIST', response.data[0])
})
.catch(() => Vue.prototype.$isSomethingWrong())
.finally(() => {
resolve(true)
})
})
},
changePreviewType: ({commit, state}, preview) => {
// Get preview type
@@ -991,6 +1007,9 @@ const actions = {
},
}
const mutations = {
LOAD_EMOJIS_LIST(state, data) {
state.emojis = data
},
UPDATE_SORTING(state) {
state.sorting.field = JSON.parse(localStorage.getItem('sorting')).field
state.sorting.sort = JSON.parse(localStorage.getItem('sorting')).sort
@@ -1035,6 +1054,7 @@ const getters = {
timezones: state=> state.timezones,
api: state => state.config.api,
config: state => state.config,
emojis: state => state.emojis,
index: state => state.index,
roles: state => state.roles,
sorting: (state) => {

View File

@@ -5,11 +5,8 @@ import router from '@/router'
import i18n from '@/i18n/index'
const defaultState = {
uploadingFilesCount: undefined,
fileInfoDetail: [],
currentFolder: undefined,
uploadingFileProgress: 0,
isProcessingFile: false,
navigation: undefined,
isSearching: false,
browseHistory: [],
@@ -264,12 +261,6 @@ const mutations = {
CHANGE_SEARCHING_STATE(state, searchState) {
state.isSearching = searchState
},
UPLOADING_FILE_PROGRESS(state, percentage) {
state.uploadingFileProgress = percentage
},
UPDATE_FILE_COUNT_PROGRESS(state, data) {
state.uploadingFilesCount = data
},
UPDATE_SHARED_ITEM(state, data) {
state.data.find(item => {
if (item.unique_id == data.item_id) item.shared = data
@@ -292,15 +283,9 @@ const mutations = {
STORE_CURRENT_FOLDER(state, folder) {
state.currentFolder = folder
},
PROCESSING_FILE(state, status) {
state.isProcessingFile = status
}
}
const getters = {
uploadingFileProgress: state => state.uploadingFileProgress,
uploadingFilesCount: state => state.uploadingFilesCount,
isProcessingFile: state => state.isProcessingFile,
fileInfoDetail: state => state.fileInfoDetail,
currentFolder: state => state.currentFolder,
browseHistory: state => state.browseHistory,

View File

@@ -4,17 +4,24 @@ import { events } from '@/bus'
import { last } from 'lodash'
import axios from 'axios'
import Vue from 'vue'
import store from '../index'
const defaultState = {
processingPopup: undefined,
fileQueue: [],
filesInQueueTotal: 0,
filesInQueueUploaded: 0,
isProcessingFile: false,
uploadingProgress: 0
}
const actions = {
downloadFolder: ({commit, getters}, folder) => {
downloadFolder: ({ commit, getters }, folder) => {
commit('PROCESSING_POPUP', {
title: i18n.t('popup_zipping.title'),
message: i18n.t('popup_zipping.message'),
message: i18n.t('popup_zipping.message')
})
// Get route
@@ -23,15 +30,15 @@ const actions = {
: '/api/zip-folder/' + folder.unique_id
axios.get(route)
.then(response => {
Vue.prototype.$downloadFile(response.data.url, response.data.name)
})
.catch(() => {
Vue.prototype.$isSomethingWrong()
})
.finally(() => {
commit('PROCESSING_POPUP', undefined)
})
.then(response => {
Vue.prototype.$downloadFile(response.data.url, response.data.name)
})
.catch(() => {
Vue.prototype.$isSomethingWrong()
})
.finally(() => {
commit('PROCESSING_POPUP', undefined)
})
},
downloadFiles: ({ commit, getters }) => {
@@ -45,7 +52,10 @@ const actions = {
? '/api/zip/public/' + router.currentRoute.params.token
: '/api/zip'
commit('ZIPPING_FILE_STATUS', true)
commit('PROCESSING_POPUP', {
title: i18n.t('popup_zipping.title'),
message: i18n.t('popup_zipping.message'),
})
axios.post(route, {
files: files
@@ -57,7 +67,7 @@ const actions = {
Vue.prototype.$isSomethingWrong()
})
.finally(() => {
commit('ZIPPING_FILE_STATUS', false)
commit('PROCESSING_POPUP', undefined)
})
},
moveItem: ({ commit, getters, dispatch }, { to_item, noSelectedItem }) => {
@@ -123,7 +133,7 @@ const actions = {
//Set focus on new folder name
setTimeout(() => {
events.$emit('newFolder:focus', response.data.unique_id)
}, 10);
}, 10)
if (getters.currentFolder.location !== 'public')
dispatch('getAppData')
@@ -169,9 +179,9 @@ const actions = {
? '/api/upload/public/' + router.currentRoute.params.token
: '/api/upload'
// Create cancel token for axios cancelation
const CancelToken = axios.CancelToken
const source = CancelToken.source()
// Create cancel token for axios cancellation
const CancelToken = axios.CancelToken,
source = CancelToken.source()
axios
.post(route, form, {
@@ -180,62 +190,72 @@ const actions = {
'Content-Type': 'application/octet-stream'
},
onUploadProgress: event => {
var percentCompleted = Math.floor(((totalUploadedSize + event.loaded) / fileSize) * 100)
commit('UPLOADING_FILE_PROGRESS', percentCompleted >= 100 ? 100 : percentCompleted)
if (percentCompleted >= 100) {
// Set processing file
if (percentCompleted >= 100)
commit('PROCESSING_FILE', true)
}
}
})
.then(response => {
resolve(response)
commit('PROCESSING_FILE', false)
// Remove first file from file queue
commit('SHIFT_FROM_FILE_QUEUE')
// Check if user is in uploading folder, if yes, than show new file
if (response.data.folder_id == getters.currentFolder.unique_id)
if (response.data.folder_id == getters.currentFolder.unique_id) {
// Add uploaded item into view
commit('ADD_NEW_ITEMS', response.data)
resolve(response)
})
.catch(error => {
commit('PROCESSING_FILE', false)
// Reset file progress
commit('UPLOADING_FILE_PROGRESS', 0)
reject(error)
switch (error.response.status) {
case 423:
events.$emit('alert:open', {
emoji: '😬😬😬',
title: i18n.t('popup_exceed_limit.title'),
message: i18n.t('popup_exceed_limit.message')
})
break
case 415:
events.$emit('alert:open', {
emoji: '😬😬😬',
title: i18n.t('popup_mimetypes_blacklist.title'),
message: i18n.t('popup_mimetypes_blacklist.message')
})
break
case 413:
events.$emit('alert:open', {
emoji: '😟😟😟',
title: i18n.t('popup_paylod_error.title'),
message: i18n.t('popup_paylod_error.message')
})
break
default:
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message')
})
break
// Increase count in files in queue uploaded for 1
commit('INCREASE_FILES_IN_QUEUE_UPLOADED')
}
// Reset uploader
commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
// Start uploading next file if file queue is not empty
if (getters.fileQueue.length) {
Vue.prototype.$handleUploading(getters.fileQueue[0])
}
// Reset upload process
if (! getters.fileQueue.length)
commit('CLEAR_UPLOAD_PROGRESS')
})
.catch(error => {
reject(error)
let messages = {
'423': {
title: i18n.t('popup_exceed_limit.title'),
message: i18n.t('popup_exceed_limit.message')
},
'415': {
title: i18n.t('popup_mimetypes_blacklist.title'),
message: i18n.t('popup_mimetypes_blacklist.message')
},
'413': {
title: i18n.t('popup_paylod_error.title'),
message: i18n.t('popup_paylod_error.message')
}
}
events.$emit('alert:open', {
emoji: '😬😬😬',
title: messages[error.response.status]['title'],
message: messages[error.response.status]['message']
})
commit('PROCESSING_FILE', false)
commit('CLEAR_UPLOAD_PROGRESS')
})
// Cancel the upload request
@@ -244,7 +264,7 @@ const actions = {
// Hide upload progress bar
commit('PROCESSING_FILE', false)
commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
commit('CLEAR_UPLOAD_PROGRESS')
})
})
},
@@ -257,28 +277,27 @@ const actions = {
// If coming no selected item dont get items to restore from fileInfoDetail
if (!item)
items = getters.fileInfoDetail
// Check if file can be restored to home directory
if (getters.currentFolder.location === 'trash')
restoreToHome = true
items.forEach(data => itemToRestore.push({
'type': data.type,
'unique_id': data.unique_id,
'unique_id': data.unique_id
}))
// Remove file preview
commit('CLEAR_FILEINFO_DETAIL')
axios
.post(getters.api + '/restore-items' ,{
.post(getters.api + '/restore-items', {
to_home: restoreToHome,
data: itemToRestore,
data: itemToRestore
})
.then(
// Remove file
items.forEach( data => commit('REMOVE_ITEM', data.unique_id) )
items.forEach(data => commit('REMOVE_ITEM', data.unique_id))
)
.catch(() => Vue.prototype.$isSomethingWrong())
},
@@ -384,11 +403,39 @@ const actions = {
const mutations = {
PROCESSING_POPUP(state, status) {
state.processingPopup = status
},
ADD_FILES_TO_QUEUE(state, file) {
state.fileQueue.push(file)
},
SHIFT_FROM_FILE_QUEUE(state) {
state.fileQueue.shift()
},
PROCESSING_FILE(state, status) {
state.isProcessingFile = status
},
UPLOADING_FILE_PROGRESS(state, percentage) {
state.uploadingProgress = percentage
},
INCREASE_FILES_IN_QUEUES_TOTAL(state, count) {
state.filesInQueueTotal += count
},
INCREASE_FILES_IN_QUEUE_UPLOADED(state) {
state.filesInQueueUploaded++
},
CLEAR_UPLOAD_PROGRESS(state) {
state.filesInQueueUploaded = 0
state.filesInQueueTotal = 0
state.fileQueue = []
}
}
const getters = {
processingPopup: state => state.processingPopup
filesInQueueUploaded: state => state.filesInQueueUploaded,
filesInQueueTotal: state => state.filesInQueueTotal,
uploadingProgress: state => state.uploadingProgress,
isProcessingFile: state => state.isProcessingFile,
processingPopup: state => state.processingPopup,
fileQueue: state => state.fileQueue
}
export default {

View File

@@ -13,9 +13,8 @@
<b class="mobile-menu-label">{{ $t('global.admin') }}</b>
<MenuItemList :navigation="AdminNavigation" />
<!--SaaS menu-->
<b class="mobile-menu-label">{{ $t('global.saas') }}</b>
<MenuItemList :navigation="SassNavigation" />
<b v-if="config.isSaaS" class="mobile-menu-label">{{ $t('global.saas') }}</b>
<MenuItemList v-if="config.isSaaS" :navigation="SassNavigation" />
</nav>
</div>
</div>
@@ -33,6 +32,9 @@
MenuItemList,
MobileHeader,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
AdminNavigation: [
@@ -54,6 +56,12 @@
routeName: 'AppOthers',
isVisible: true,
},
{
icon: 'monitor',
title: this.$t('admin_menu.pages'),
routeName: 'Pages',
isVisible: true,
},
],
SassNavigation: [
{
@@ -68,12 +76,6 @@
routeName: 'Invoices',
isVisible: true,
},
{
icon: 'monitor',
title: this.$t('admin_menu.pages'),
routeName: 'Pages',
isVisible: true,
},
]
}
},

View File

@@ -60,9 +60,9 @@
</div>
<!--Single file page-->
<div v-if="sharedDetail.type === 'file' && isPageFiles" id="single-file">
<div v-if="sharedDetail && sharedDetail.type === 'file' && isPageFiles" id="single-file">
<div class="single-file-wrapper">
<FileItemGrid v-if="sharedFile" :data="sharedFile" :context-menu="false"/>
<FileItemGrid v-if="sharedFile" :item="sharedFile" :context-menu="false"/>
<ButtonBase @click.native="download" class="download-button" button-style="theme">
{{ $t('page_shared.download_file') }}
@@ -71,7 +71,7 @@
</div>
<!--Multiple items view page-->
<div v-if="sharedDetail.type === 'folder' && isPageFiles"
<div v-if="sharedDetail && sharedDetail.type === 'folder' && isPageFiles"
@contextmenu.prevent.capture="contextMenu($event, undefined)"
id="viewport">

View File

@@ -193,4 +193,7 @@ Route::group(['middleware' => ['auth:api', 'auth.shared', 'auth.master', 'scope:
Route::get('/zip-folder/{unique_id}', 'FileFunctions\EditItemsController@user_zip_folder');
Route::post('/upload', 'FileFunctions\EditItemsController@user_upload');
Route::post('/move', 'FileFunctions\EditItemsController@user_move');
//Get Emojis List
Route::get('/emojis-list', 'AppFunctionsController@get_emojis_list');
});

View File

@@ -1,2 +0,0 @@
*
!.gitignore