diff --git a/config/vuefilemanager.php b/config/vuefilemanager.php index 580ae9ee..784818f8 100644 --- a/config/vuefilemanager.php +++ b/config/vuefilemanager.php @@ -61,6 +61,10 @@ return [ ], ], + 'paginate' => [ + 'perPage' => 8, + ], + // The update versions which need to run upgrade process 'updates' => [ '2_0_10', diff --git a/package.json b/package.json index 74015fb8..faeff722 100644 --- a/package.json +++ b/package.json @@ -23,26 +23,26 @@ "resolve-url-loader": "^2.3.1", "sass": "^1.49.11", "sass-loader": "^8.0.2", - "tailwindcss": "^3.0.23", + "tailwindcss": "^3.0.24", "tailwindcss-debug-screens": "^2.2.1", "vue-loader": "^15.9.8", "vue-template-compiler": "^2.6.14" }, "dependencies": { "@paypal/paypal-js": "^4.2.2", - "@stripe/stripe-js": "^1.26.0", + "@stripe/stripe-js": "^1.29.0", "lodash": "^4.17.21", "node-sass": "^4.14.1", "pdfvuer": "^1.9.2", "tailwind-scrollbar-hide": "^1.1.7", "twemoji": "^13.1.1", - "v-click-outside": "^3.1.2", + "v-click-outside": "^3.2.0", "vee-validate": "^3.4.14", "vue": "^2.6.14", "vue-feather-icons": "^5.1.0", "vue-i18n": "^8.27.1", "vue-recaptcha-v3": "^1.9.0", - "vue-router": "^3.5.3", + "vue-router": "^3.5.4", "vuex": "^3.6.2" } } diff --git a/resources/js/components/EntriesView/FileBrowser.vue b/resources/js/components/EntriesView/FileBrowser.vue index 7aea977c..c440e801 100644 --- a/resources/js/components/EntriesView/FileBrowser.vue +++ b/resources/js/components/EntriesView/FileBrowser.vue @@ -11,6 +11,7 @@ @dragover="dragEnter" @dragleave="dragLeave" @dragover.prevent + @scroll="infiniteScroll" tabindex="-1" @click.self="deselect" > @@ -24,6 +25,15 @@ :key="item.data.id" :item="item" /> + + +
+ +
@@ -31,14 +41,17 @@ import ItemHandler from './ItemHandler' import { events } from '../../bus' import { mapGetters } from 'vuex' +import Spinner from './Spinner' +import { debounce } from 'lodash' export default { name: 'FileBrowser', components: { ItemHandler, + Spinner }, computed: { - ...mapGetters(['isVisibleSidebar', 'currentFolder', 'itemViewType', 'clipboard', 'entries', 'config']), + ...mapGetters(['isVisibleSidebar', 'currentFolder', 'itemViewType', 'clipboard', 'entries', 'config', 'paginate']), draggedItems() { // Set opacity for dragged items if (!this.clipboard.includes(this.draggingId)) { @@ -49,14 +62,39 @@ export default { return this.clipboard } }, + canLoadMoreEntries() { + return this.paginate?.currentPage !== this.paginate?.lastPage + }, + showInfiniteLoadSpinner() { + return this.canLoadMoreEntries && this.entries.length !== 0 && this.paginate.perPage <= this.entries.length + }, }, data() { return { draggingId: undefined, isDragging: false, + isLoadingNewEntries: false, } }, methods: { + infiniteScroll: debounce(function () { + if (this.isInfinityLoaderAtBottomPage() && this.canLoadMoreEntries && !this.isLoadingNewEntries) { + this.isLoadingNewEntries = true + + this.$getDataByLocation(this.paginate.currentPage + 1) + .then(() => this.isLoadingNewEntries = false) + } + }, 150), + isInfinityLoaderAtBottomPage() { + let rect = this.$refs.infinityLoader.getBoundingClientRect() + + return ( + rect.bottom > 0 && + rect.right > 0 && + rect.left < (window.innerWidth || document.documentElement.clientWidth) && + rect.top < (window.innerHeight || document.documentElement.clientHeight) + ); + }, deleteItems() { if ((this.clipboard.length > 0 && this.$checkPermission('master')) || this.$checkPermission('editor')) { this.$store.dispatch('deleteItem') @@ -81,7 +119,7 @@ export default { // Store dragged folder this.draggingId = data - // TODO: founded issue on firefox + // TODO: found issue on firefox }, dragFinish(data, event) { if (event.dataTransfer.items.length === 0) { @@ -131,6 +169,11 @@ export default { }, }, created() { + // Track document scrolling to load new entries if needed + if (window.innerWidth <= 1024) { + document.addEventListener('scroll', this.infiniteScroll) + } + events.$on('drop', () => { this.isDragging = false diff --git a/resources/js/components/EntriesView/NavigationSharePanel.vue b/resources/js/components/EntriesView/NavigationSharePanel.vue index d137a5a2..40267560 100644 --- a/resources/js/components/EntriesView/NavigationSharePanel.vue +++ b/resources/js/components/EntriesView/NavigationSharePanel.vue @@ -115,7 +115,7 @@ export default { 'isDarkMode', ]), favourites() { - return this.user.data.relationships.favourites.data.attributes.folders + return this.user.data.relationships.favourites.attributes.folders }, storage() { return this.$store.getters.user.data.attributes.storage diff --git a/resources/js/components/EntriesView/PanelNavigationFiles.vue b/resources/js/components/EntriesView/PanelNavigationFiles.vue index 3c4d2fbc..ba212d44 100644 --- a/resources/js/components/EntriesView/PanelNavigationFiles.vue +++ b/resources/js/components/EntriesView/PanelNavigationFiles.vue @@ -132,7 +132,7 @@ export default { computed: { ...mapGetters(['isVisibleNavigationBars', 'navigation', 'clipboard', 'config', 'user']), favourites() { - return this.user.data.relationships.favourites.data + return this.user.data.relationships.favourites }, storage() { return this.$store.getters.user.data.attributes.storage diff --git a/resources/js/components/Spotlight/Spotlight.vue b/resources/js/components/Spotlight/Spotlight.vue index 80855f64..ed3e5e0c 100644 --- a/resources/js/components/Spotlight/Spotlight.vue +++ b/resources/js/components/Spotlight/Spotlight.vue @@ -900,13 +900,13 @@ export default { .then((response) => { // Show user result if (this.activeFilter === 'users') { - this.results = response.data.data + this.results = response.data } // Show file result if (!this.activeFilter) { - let files = response.data.files.data - let folders = response.data.folders.data + let files = response.data.files + let folders = response.data.folders this.results = folders.concat(files) } diff --git a/resources/js/helpers/functionHelpers.js b/resources/js/helpers/functionHelpers.js index 8cba68aa..a2d39754 100644 --- a/resources/js/helpers/functionHelpers.js +++ b/resources/js/helpers/functionHelpers.js @@ -350,19 +350,19 @@ const FunctionHelpers = { }[this.$router.currentRoute.name] } - Vue.prototype.$getDataByLocation = function () { + Vue.prototype.$getDataByLocation = async function (page) { let routes = { - RequestUpload: ['getUploadRequestFolder', router.currentRoute.params.id || undefined ], - Public: ['getSharedFolder', router.currentRoute.params.id || undefined], - Files: ['getFolder', router.currentRoute.params.id || undefined], - RecentUploads: ['getRecentUploads'], - MySharedItems: ['getMySharedItems'], - Trash: ['getTrash', router.currentRoute.params.id || undefined], - TeamFolders: ['getTeamFolder', router.currentRoute.params.id || undefined], - SharedWithMe: ['getSharedWithMeFolder', router.currentRoute.params.id || undefined], + RequestUpload: ['getUploadRequestFolder', {page:page, id:router.currentRoute.params.id || undefined} ], + Public: ['getSharedFolder', {page:page, id:router.currentRoute.params.id || undefined}], + Files: ['getFolder', {page:page, id:router.currentRoute.params.id || undefined}], + RecentUploads: ['getRecentUploads', page], + MySharedItems: ['getMySharedItems', page], + Trash: ['getTrash', {page:page , id:router.currentRoute.params.id || undefined}], + TeamFolders: ['getTeamFolder',{page:page, id:router.currentRoute.params.id || undefined}], + SharedWithMe: ['getSharedWithMeFolder',{page:page, id:router.currentRoute.params.id || undefined}], } - store.dispatch(...routes[router.currentRoute.name]) + await store.dispatch(...routes[router.currentRoute.name]) } Vue.prototype.$getPaymentLogo = function (driver) { diff --git a/resources/js/helpers/itemHelpers.js b/resources/js/helpers/itemHelpers.js index 635272d0..da1dcaa5 100644 --- a/resources/js/helpers/itemHelpers.js +++ b/resources/js/helpers/itemHelpers.js @@ -17,7 +17,7 @@ const itemHelpers = { } Vue.prototype.$toggleFavourites = function (entry) { - let favourites = store.getters.user.data.relationships.favourites.data + let favourites = store.getters.user.data.relationships.favourites // Check if folder is in favourites and then add/remove from favourites if (favourites && !favourites.find((el) => el.data.id === entry.data.id)) { diff --git a/resources/js/store/modules/fileBrowser.js b/resources/js/store/modules/fileBrowser.js index 26718974..f77564f3 100644 --- a/resources/js/store/modules/fileBrowser.js +++ b/resources/js/store/modules/fileBrowser.js @@ -6,45 +6,50 @@ import i18n from '../../i18n' const defaultState = { currentFolder: undefined, + isMultiSelectMode: false, fastPreview: undefined, navigation: undefined, - isMultiSelectMode: false, + paginate: undefined, isLoading: true, clipboard: [], entries: [], } const actions = { - getFolder: ({ commit, getters }, id) => { - commit('LOADING_STATE', { loading: true, data: [] }) + getFolder: ({ commit, getters },{page, id}) => { + return new Promise ((resolve, reject) => { + if(! page) + commit('LOADING_STATE', { loading: true, data: [] }) - axios - .get(`${getters.api}/browse/folders/${id || 'all'}${getters.sorting.URI}`) - .then((response) => { - let folders = response.data.folders.data - let files = response.data.files.data + axios + .get(`${getters.api}/browse/folders/${id || 'all'}${getters.sorting.URI}&page=${currentPage}`) + .then((response) => { + commit('SET_PAGINATE', response.data.meta.paginate) - commit('LOADING_STATE', { - loading: false, - data: folders.concat(files), - }) - commit('SET_CURRENT_FOLDER', response.data.root) - - events.$emit('scrollTop') - }) - .catch((error) => { - // Redirect if unauthenticated - if ([401, 403].includes(error.response.status)) { - commit('SET_AUTHORIZED', false) - router.push({ name: 'SignIn' }) - } else { - // Show error message - events.$emit('alert:open', { - title: i18n.t('popup_error.title'), - message: i18n.t('popup_error.message'), + commit('LOADING_STATE', { + loading: false, + data: response.data.data, }) - } - }) + commit('SET_CURRENT_FOLDER', response.data.meta.root) + + events.$emit('scrollTop') + + resolve(response); + }) + .catch((error) => { + // Redirect if unauthenticated + if ([401, 403].includes(error.response.status)) { + commit('SET_AUTHORIZED', false) + router.push({ name: 'SignIn' }) + } else { + // Show error message + events.$emit('alert:open', { + title: i18n.t('popup_error.title'), + message: i18n.t('popup_error.message'), + }) + } + }) + }) }, getRecentUploads: ({ commit, getters }) => { commit('LOADING_STATE', { loading: true, data: [] }) @@ -125,8 +130,15 @@ const actions = { } const mutations = { + SET_PAGINATE(state, payload) { + state.paginate = payload + }, LOADING_STATE(state, payload) { - state.entries = payload.data + if(payload.data.length === 0) { + state.entries = [] + } else { + state.entries.push(...payload.data) + } state.isLoading = payload.loading }, SET_CURRENT_FOLDER(state, folder) { @@ -220,6 +232,7 @@ const getters = { navigation: (state) => state.navigation, clipboard: (state) => state.clipboard, isLoading: (state) => state.isLoading, + paginate: (state) => state.paginate, entries: (state) => state.entries, } diff --git a/resources/js/store/modules/uploadRequest.js b/resources/js/store/modules/uploadRequest.js index 4bca2915..53ed3f01 100644 --- a/resources/js/store/modules/uploadRequest.js +++ b/resources/js/store/modules/uploadRequest.js @@ -15,8 +15,8 @@ const actions = { axios .get(`/api/file-request/${router.currentRoute.params.token}/browse/${id || 'all'}${getters.sorting.URI}`) .then((response) => { - let folders = response.data.folders.data - let files = response.data.files.data + let folders = response.data.folders + let files = response.data.files commit('LOADING_STATE', { loading: false, @@ -52,6 +52,11 @@ const actions = { commit('SET_CURRENT_FOLDER', response.data.data.relationships.folder) } }) + .catch((error) => { + Vue.prototype.$isSomethingWrong() + + reject(error) + }) }) }, closeUploadRequest: ({ commit }) => { diff --git a/resources/js/views/FileView/Files.vue b/resources/js/views/FileView/Files.vue index 4294cdfb..e9707e73 100644 --- a/resources/js/views/FileView/Files.vue +++ b/resources/js/views/FileView/Files.vue @@ -254,7 +254,7 @@ export default { return this.item && this.item.data.type === 'folder' }, isInFavourites() { - return this.user.data.relationships.favourites.data.find((el) => el.data.id === this.item.data.id) + return this.user.data.relationships.favourites.find((el) => el.data.id === this.item.data.id) }, hasFile() { return this.clipboard.find((item) => item.data.type !== 'folder') @@ -266,7 +266,7 @@ export default { } }, created() { - this.$store.dispatch('getFolder', this.$route.params.id) + this.$store.dispatch('getFolder', {page:null, id:this.$route.params.id}) events.$on('context-menu:show', (event, item) => (this.item = item)) events.$on('context-menu:current-folder', (folder) => (this.item = folder)) diff --git a/resources/js/views/FileView/MySharedItems.vue b/resources/js/views/FileView/MySharedItems.vue index 662a43d6..a3ef17c4 100644 --- a/resources/js/views/FileView/MySharedItems.vue +++ b/resources/js/views/FileView/MySharedItems.vue @@ -157,7 +157,7 @@ export default { return this.item && this.item.type === 'folder' }, isInFavourites() { - return this.user.data.relationships.favourites.data.find((el) => el.id === this.item.id) + return this.user.data.relationships.favourites.find((el) => el.id === this.item.id) }, hasFile() { return this.clipboard.find((item) => item.type !== 'folder') diff --git a/resources/js/views/FileView/Public.vue b/resources/js/views/FileView/Public.vue index 637a8d6d..7a77ac6b 100644 --- a/resources/js/views/FileView/Public.vue +++ b/resources/js/views/FileView/Public.vue @@ -234,7 +234,7 @@ export default { }, }, created() { - this.$store.dispatch('getSharedFolder', this.$route.params.id) + this.$store.dispatch('getSharedFolder', {page:null, id:this.$route.params.id}) events.$on('context-menu:show', (event, item) => (this.item = item)) events.$on('mobile-context-menu:show', (item) => (this.item = item)) diff --git a/resources/js/views/FileView/SharedWithMe.vue b/resources/js/views/FileView/SharedWithMe.vue index 17123ca4..63194ad5 100644 --- a/resources/js/views/FileView/SharedWithMe.vue +++ b/resources/js/views/FileView/SharedWithMe.vue @@ -242,7 +242,7 @@ export default { }, }, mounted() { - this.$store.dispatch('getSharedWithMeFolder', this.$route.params.id) + this.$store.dispatch('getSharedWithMeFolder',{page:null, id:this.$route.params.id}) events.$on('context-menu:show', (event, item) => (this.item = item)) events.$on('mobile-context-menu:show', (item) => (this.item = item)) @@ -257,7 +257,7 @@ export default { if (this.$route.params.id) { this.$router.push({ name: 'SharedWithMe' }) } else { - this.$store.dispatch('getSharedWithMeFolder', undefined) + this.$store.dispatch('getSharedWithMeFolder',{page:null, id:undefined}) } events.$emit('toaster', { diff --git a/resources/js/views/FileView/TeamFolders.vue b/resources/js/views/FileView/TeamFolders.vue index 2b41cbae..4752c2de 100644 --- a/resources/js/views/FileView/TeamFolders.vue +++ b/resources/js/views/FileView/TeamFolders.vue @@ -281,7 +281,7 @@ export default { return this.item && this.item.data.type === 'folder' }, isInFavourites() { - return this.user.data.relationships.favourites.data.find((el) => el.id === this.item.id) + return this.user.data.relationships.favourites.find((el) => el.id === this.item.id) }, hasFile() { return this.clipboard.find((item) => item.type !== 'folder') @@ -293,7 +293,7 @@ export default { } }, mounted() { - this.$store.dispatch('getTeamFolder', this.$route.params.id) + this.$store.dispatch('getTeamFolder', {page:null, id:this.$route.params.id}) events.$on('context-menu:show', (event, item) => (this.item = item)) events.$on('mobile-context-menu:show', (item) => (this.item = item)) diff --git a/resources/js/views/FileView/Trash.vue b/resources/js/views/FileView/Trash.vue index d2468cbc..3c6ced40 100644 --- a/resources/js/views/FileView/Trash.vue +++ b/resources/js/views/FileView/Trash.vue @@ -149,7 +149,7 @@ export default { } }, created() { - this.$store.dispatch('getTrash', this.$route.params.id) + this.$store.dispatch('getTrash', {page:null, id:this.$route.params.id}) events.$on('context-menu:show', (event, item) => (this.item = item)) events.$on('mobile-context-menu:show', (item) => (this.item = item)) diff --git a/src/App/Users/Models/User.php b/src/App/Users/Models/User.php index d63dfa55..1051d067 100644 --- a/src/App/Users/Models/User.php +++ b/src/App/Users/Models/User.php @@ -160,8 +160,7 @@ class User extends Authenticatable implements MustVerifyEmail ->with([ 'parent:id,name', 'shared:token,id,item_id,permission,is_protected,expire_in', - ]) - ->take(40); + ]); } /** diff --git a/src/Domain/Browsing/Controllers/BrowseFolderController.php b/src/Domain/Browsing/Controllers/BrowseFolderController.php index 82d94b9e..6a19e726 100644 --- a/src/Domain/Browsing/Controllers/BrowseFolderController.php +++ b/src/Domain/Browsing/Controllers/BrowseFolderController.php @@ -16,24 +16,49 @@ class BrowseFolderController ): array { $root_id = Str::isUuid($id) ? $id : null; + $folderQuery = [ + 'parent_id' => $root_id, + 'team_folder' => false, + 'user_id' => Auth::id(), + 'deleted_at' => null, + ]; + + $fileQuery = [ + 'parent_id' => $root_id, + 'user_id' => Auth::id(), + 'deleted_at' => null, + ]; + + list($foldersTake, $foldersSkip, $filesTake, $filesSkip, $totalItemsCount) = getRecordsCount($folderQuery, $fileQuery, request()->input('page')); + $folders = Folder::with(['parent:id,name', 'shared:token,id,item_id,permission,is_protected,expire_in']) - ->where('parent_id', $root_id) - ->where('team_folder', false) - ->where('user_id', Auth::id()) + ->where($folderQuery) ->sortable() + ->skip($foldersSkip) + ->take($foldersTake) ->get(); - + $files = File::with(['parent:id,name', 'shared:token,id,item_id,permission,is_protected,expire_in']) - ->where('parent_id', $root_id) - ->where('user_id', Auth::id()) + ->where($fileQuery) ->sortable() + ->skip($filesSkip) + ->take($filesTake) ->get(); + + $entries = collect([ + $folders ? json_decode((new FolderCollection($folders))->toJson(), true) : null, + $files ? json_decode((new FilesCollection($files))->toJson(), true) : null, + ])->collapse(); + + list($paginate, $links) = generatePaginationCounts($totalItemsCount); - // Collect folders and files to single array return [ - 'folders' => new FolderCollection($folders), - 'files' => new FilesCollection($files), - 'root' => $root_id ? new FolderResource(Folder::findOrFail($root_id)) : null, + 'data' => $entries, + 'links' => $links, + 'meta' => [ + 'paginate' => $paginate, + 'root' => $root_id ? new FolderResource(Folder::findOrFail($root_id)) : null, + ], ]; } } diff --git a/src/Domain/Browsing/Controllers/BrowseLatestFilesController.php b/src/Domain/Browsing/Controllers/BrowseLatestFilesController.php index 1f1eb085..5572300b 100644 --- a/src/Domain/Browsing/Controllers/BrowseLatestFilesController.php +++ b/src/Domain/Browsing/Controllers/BrowseLatestFilesController.php @@ -2,12 +2,12 @@ namespace Domain\Browsing\Controllers; use App\Users\Models\User; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Domain\Files\Resources\FilesCollection; class BrowseLatestFilesController { - public function __invoke(): array + public function __invoke(Request $request): array { $user = User::with([ 'latestUploads' => fn ($query) => $query->sortable(['created_at' => 'desc']), @@ -15,9 +15,15 @@ class BrowseLatestFilesController ->where('id', Auth::id()) ->first(); + list($data, $paginate, $links) = groupPaginate($request, null, $user->latestUploads); + return [ - 'files' => new FilesCollection($user->latestUploads), - 'root' => null, + 'data' => $data, + 'links' => $links, + 'meta' => [ + 'paginate' => $paginate, + 'root' => null, + ], ]; } } diff --git a/src/Domain/Browsing/Controllers/BrowseSharedItemsController.php b/src/Domain/Browsing/Controllers/BrowseSharedItemsController.php index 902af769..03282375 100644 --- a/src/Domain/Browsing/Controllers/BrowseSharedItemsController.php +++ b/src/Domain/Browsing/Controllers/BrowseSharedItemsController.php @@ -1,16 +1,15 @@ sortable() ->get(); + list($data, $paginate, $links) = groupPaginate($request, $folders, $files); + // Collect folders and files to single array return [ - 'folders' => new FolderCollection($folders), - 'files' => new FilesCollection($files), - 'root' => null, + 'data' => $data, + 'links' => $links, + 'meta' => [ + 'paginate' => $paginate, + 'root' => null, + ], ]; } } diff --git a/src/Domain/Files/Resources/FilesCollection.php b/src/Domain/Files/Resources/FilesCollection.php index 28f265a5..e2c50f93 100644 --- a/src/Domain/Files/Resources/FilesCollection.php +++ b/src/Domain/Files/Resources/FilesCollection.php @@ -1,16 +1,15 @@ $this->collection, - ]; + return $this->collection; } } diff --git a/src/Domain/Folders/Resources/FolderCollection.php b/src/Domain/Folders/Resources/FolderCollection.php index 5b00fcf3..4fd429c4 100644 --- a/src/Domain/Folders/Resources/FolderCollection.php +++ b/src/Domain/Folders/Resources/FolderCollection.php @@ -1,16 +1,15 @@ $this->collection, - ]; + return $this->collection; } } diff --git a/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php b/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php index 50cad2c0..f58bde7c 100644 --- a/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php +++ b/src/Domain/Teams/Controllers/BrowseSharedWithMeController.php @@ -3,17 +3,16 @@ namespace Domain\Teams\Controllers; use Str; use Gate; +use Illuminate\Http\Request; use Domain\Files\Models\File; use Domain\Folders\Models\Folder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; -use Domain\Files\Resources\FilesCollection; use Domain\Folders\Resources\FolderResource; -use Domain\Folders\Resources\FolderCollection; class BrowseSharedWithMeController { - public function __invoke($id): array + public function __invoke(Request $request, $id): array { $id = Str::isUuid($id) ? $id : null; @@ -46,11 +45,16 @@ class BrowseSharedWithMeController ->get(); } + list($data, $paginate, $links) = groupPaginate($request, $folders, $files ?? null); + return [ - 'root' => $id ? new FolderResource(Folder::findOrFail($id)) : null, - 'folders' => new FolderCollection($folders), - 'files' => isset($files) ? new FilesCollection($files) : new FilesCollection([]), + 'data' => $data, 'teamFolder' => $id ? new FolderResource($teamFolder) : null, + 'links' => $links, + 'meta' => [ + 'paginate' => $paginate, + 'root' => $id ? new FolderResource(Folder::findOrFail($id)) : null, + ], ]; } } diff --git a/src/Domain/Teams/Controllers/TeamFoldersController.php b/src/Domain/Teams/Controllers/TeamFoldersController.php index 9048cf45..97aea72d 100644 --- a/src/Domain/Teams/Controllers/TeamFoldersController.php +++ b/src/Domain/Teams/Controllers/TeamFoldersController.php @@ -2,6 +2,7 @@ namespace Domain\Teams\Controllers; use Illuminate\Support\Str; +use Illuminate\Http\Request; use Domain\Files\Models\File; use Domain\Folders\Models\Folder; use Illuminate\Http\JsonResponse; @@ -10,10 +11,8 @@ use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; use Domain\Teams\Models\TeamFolderMember; use Domain\Teams\DTO\CreateTeamFolderData; -use Domain\Files\Resources\FilesCollection; use Domain\Folders\Resources\FolderResource; use Domain\Teams\Actions\UpdateMembersAction; -use Domain\Folders\Resources\FolderCollection; use Domain\Teams\Actions\UpdateInvitationsAction; use Domain\Teams\Requests\CreateTeamFolderRequest; use Domain\Teams\Requests\UpdateTeamFolderMembersRequest; @@ -28,7 +27,7 @@ class TeamFoldersController extends Controller ) { } - public function show($id): array + public function show(Request $request, $id): array { $id = Str::isUuid($id) ? $id : null; @@ -51,12 +50,17 @@ class TeamFoldersController extends Controller ->get(); } + list($data, $paginate, $links) = groupPaginate($request, $folders, $files ?? null); + // Collect folders and files to single array return [ - 'folders' => new FolderCollection($folders), - 'files' => isset($files) ? new FilesCollection($files) : new FilesCollection([]), - 'root' => $id ? new FolderResource(Folder::findOrFail($id)) : null, + 'data' => $data, 'teamFolder' => $id ? new FolderResource(Folder::findOrFail($id)->getLatestParent()) : null, + 'links' => $links, + 'meta' => [ + 'paginate' => $paginate, + 'root' => $id ? new FolderResource(Folder::findOrFail($id)) : null, + ], ]; } diff --git a/src/Support/helpers.php b/src/Support/helpers.php index 6ecd7143..27e8046d 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -9,6 +9,7 @@ use Domain\Files\Models\File; use Domain\Sharing\Models\Share; use Domain\Folders\Models\Folder; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Domain\Settings\Models\Setting; use Illuminate\Support\Facades\Http; use Illuminate\Database\Eloquent\Model; @@ -1208,4 +1209,93 @@ if (! function_exists('extractItemsFromGetAttribute')) { ]; }); } + + if (! function_exists('generatePaginationCounts')) { + /** + * Group paginate of Foldes and Files + */ + function generatePaginationCounts( + int $totalItemsCount + ) : array { + $perPage = config('vuefilemanager.paginate.perPage'); + $currentPage = request()->input('page') === 'all' ? 1 : (int) request()->input('page'); + + $uri = request()->fullUrl(); + $lastPage = ceil($totalItemsCount / $perPage); + + return [ + [ + 'currentPage' => $currentPage, + 'from' => 1, + 'lastPage' => $lastPage, + 'path' => $uri, + 'perPage' => $perPage, + 'to' => $perPage, + 'total' => $totalItemsCount, + ], + [ + 'first' => $uri . '&page=1', + 'last' => $uri . '&page=' . $lastPage, + 'next' => $currentPage == $lastPage ? null : $uri . '&page=' . $currentPage + 1, + 'prev' => $currentPage == 1 ? null : $uri . '&page=' . $currentPage - 1, + ], + ]; + } + } + + if (! function_exists('getRecordsCount')) { + /** + * Get count of items from the Database + */ + function getRecordsCount( + array $folderQuery, + array $fileQuery, + string $page + ) : array { + $perPage = config('vuefilemanager.paginate.perPage'); + $currentPage = $page === 'all' ? 1 : (int) $page; + + $foldersSkip = 0; + $foldersTake = 0; + $filesSkip = 0; + $filesTake = 0; + + $foldersCount = DB::table('folders') + ->where($folderQuery) + ->count(); + + $filesCount = DB::table('files') + ->where($fileQuery) + ->count(); + + $totalItemsCount = $foldersCount + $filesCount; + + if ($page !== 'all') { + // Folders pages + if ($foldersCount >= $currentPage * $perPage) { + $foldersTake = $perPage; + $foldersSkip = ($currentPage - 1) * $perPage; + } + + // Mixed page + if ($foldersCount < $currentPage * $perPage && ceil($currentPage) === ceil($foldersCount / $perPage)) { + $foldersSkip = ($currentPage - 1) * $perPage; + $foldersTake = $foldersCount - $foldersSkip; + $filesTake = ($currentPage * $perPage) - $foldersCount; + $filesSkip = 0; + } + + // Files pages + if ($currentPage > ceil($foldersCount / $perPage)) { + $filesTake = $perPage; + $filesSkip = ((ceil($foldersCount / $perPage) * $perPage) - $foldersCount) + ($currentPage - (ceil($foldersCount / $perPage)) - 1) * $perPage; + } + } else { + $foldersTake = $foldersCount; + $filesTake = $filesCount; + } + + return [$foldersTake, $foldersSkip, $filesTake, $filesSkip, $totalItemsCount]; + } + } } diff --git a/tests/Support/Helpers/HelperTest.php b/tests/Support/Helpers/HelperTest.php index c5d156d9..6c44ea0b 100644 --- a/tests/Support/Helpers/HelperTest.php +++ b/tests/Support/Helpers/HelperTest.php @@ -2,6 +2,10 @@ namespace Tests\Support\Helpers; use Tests\TestCase; +use App\Users\Models\User; +use Domain\Files\Models\File; +use Domain\Folders\Models\Folder; +use Illuminate\Support\Facades\Config; class HelperTest extends TestCase { @@ -25,4 +29,57 @@ class HelperTest extends TestCase $this->assertEquals('Jane', $thirdTest['first_name']); $this->assertEquals('', $thirdTest['last_name']); } + + /** + * @test + */ + public function it_test_get_records_count() + { + $user = User::factory() + ->hasSettings() + ->create(); + + Folder::factory() + ->count(12) + ->create([ + 'user_id' => $user->id, + 'parent_id' => null, + ]); + + File::factory() + ->count(13) + ->create([ + 'user_id' => $user->id, + 'parent_id' => null, + ]); + + $folderQuery = [ + 'parent_id' => null, + 'team_folder' => false, + 'user_id' => $user->id, + 'deleted_at' => null, + ]; + + $fileQuery = [ + 'parent_id' => null, + 'user_id' => $user->id, + 'deleted_at' => null, + ]; + + Config::set('vuefilemanager.paginate.perPage', 5); + + // getRecordsCunt returned array [foldersTake, foldersSkip, filesTake, filesSkip, totalItemsCount] + + // Get folders page + $this->assertEquals([5, 0, 0, 0, 25], getRecordsCount($folderQuery, $fileQuery, '1')); + + // Get mixed page + $this->assertEquals([2, 10, 3, 0, 25], getRecordsCount($folderQuery, $fileQuery, '3')); + + // Get files page + $this->assertEquals([0, 0, 5, 8, 25], getRecordsCount($folderQuery, $fileQuery, '5')); + + // Get all pages + $this->assertEquals([12, 0, 13, 0, 25], getRecordsCount($folderQuery, $fileQuery, 'all')); + } }