From f42a82aaeea4a2b371ea3579fa89f4b0f7b6409e Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Wed, 15 Mar 2023 07:49:05 +0100 Subject: [PATCH] Add global loading indicator for long running tasks --- .../src/views/Users.vue | 17 +- .../src/helpers/resource/actions/transfer.ts | 30 +++- .../conflictHandling/conflictDialog.ts | 2 +- .../src/mixins/actions/downloadArchive.ts | 39 ++--- .../src/mixins/actions/emptyTrashBin.ts | 41 ++--- .../src/mixins/actions/restore.ts | 18 ++- .../src/mixins/deleteResources.ts | 147 ++++++++++-------- packages/web-app-files/src/store/actions.ts | 41 +++-- packages/web-app-files/src/store/mutations.ts | 5 + .../src/views/spaces/GenericSpace.vue | 15 +- .../src/components/LoadingIndicator.vue | 58 +++++++ packages/web-pkg/src/services/index.ts | 1 + .../web-pkg/src/services/loadingService.ts | 100 ++++++++++++ .../web-runtime/src/layouts/Application.vue | 5 +- 14 files changed, 376 insertions(+), 143 deletions(-) create mode 100644 packages/web-pkg/src/components/LoadingIndicator.vue create mode 100644 packages/web-pkg/src/services/loadingService.ts diff --git a/packages/web-app-admin-settings/src/views/Users.vue b/packages/web-app-admin-settings/src/views/Users.vue index a6bd6e1c7c9..23107cf10f2 100644 --- a/packages/web-app-admin-settings/src/views/Users.vue +++ b/packages/web-app-admin-settings/src/views/Users.vue @@ -200,6 +200,7 @@ import GroupsModal from '../components/Users/GroupsModal.vue' import { useRemoveFromGroups } from '../mixins/users/removeFromGroups' import { useAddToGroups } from '../mixins/users/addToGroups' import { configurationManager } from 'web-pkg' +import { loadingService } from 'web-pkg/src/services' export default defineComponent({ name: 'UsersView', @@ -433,10 +434,10 @@ export default defineComponent({ } return acc }, addUsersToGroupsRequests) - await Promise.all(addUsersToGroupsRequests) - const usersResponse = await Promise.all( - usersToFetch.map((userId) => unref(graphClient).users.getUser(userId)) - ) + const usersResponse = await loadingService.addTask(async () => { + await Promise.all(addUsersToGroupsRequests) + return Promise.all(usersToFetch.map((userId) => unref(graphClient).users.getUser(userId))) + }) for (const { data: updatedUser } of usersResponse) { const userIndex = unref(users).findIndex((user) => user.id === updatedUser.id) unref(users)[userIndex] = updatedUser @@ -476,10 +477,10 @@ export default defineComponent({ } return acc }, removeUsersToGroupsRequests) - await Promise.all(removeUsersToGroupsRequests) - const usersResponse = await Promise.all( - usersToFetch.map((userId) => unref(graphClient).users.getUser(userId)) - ) + const usersResponse = await loadingService.addTask(async () => { + await Promise.all(removeUsersToGroupsRequests) + return Promise.all(usersToFetch.map((userId) => unref(graphClient).users.getUser(userId))) + }) for (const { data: updatedUser } of usersResponse) { const userIndex = unref(users).findIndex((user) => user.id === updatedUser.id) unref(users)[userIndex] = updatedUser diff --git a/packages/web-app-files/src/helpers/resource/actions/transfer.ts b/packages/web-app-files/src/helpers/resource/actions/transfer.ts index 116a522dec5..3b75c239132 100644 --- a/packages/web-app-files/src/helpers/resource/actions/transfer.ts +++ b/packages/web-app-files/src/helpers/resource/actions/transfer.ts @@ -1,12 +1,13 @@ import { Resource } from 'web-client' import { join } from 'path' import { SpaceResource } from 'web-client/src/helpers' -import { ClientService } from 'web-pkg/src/services' +import { ClientService, loadingService, LoadingTaskState } from 'web-pkg/src/services' import { ConflictDialog, ResolveStrategy, isResourceBeeingMovedToSameLocation, - resolveFileNameDuplicate + resolveFileNameDuplicate, + FileConflict } from '../conflictHandling' import { TransferType } from '.' @@ -106,7 +107,6 @@ export class ResourceTransfer extends ConflictDialog { transferType = TransferType.COPY } - const errors = [] const targetFolderResources = ( await this.clientService.webdav.listFiles(this.targetSpace, this.targetFolder) ).children @@ -116,9 +116,30 @@ export class ResourceTransfer extends ConflictDialog { this.targetFolder, targetFolderResources ) + + return loadingService.addTask( + ({ setProgress }) => { + return this.moveResources( + resolvedConflicts, + targetFolderResources, + transferType, + setProgress + ) + }, + { indeterminate: false } + ) + } + + private async moveResources( + resolvedConflicts: FileConflict[], + targetFolderResources: Resource[], + transferType: TransferType, + setProgress: (args: LoadingTaskState) => void + ) { const movedResources: Resource[] = [] + const errors = [] - for (let resource of this.resourcesToMove) { + for (let [i, resource] of this.resourcesToMove.entries()) { // shallow copy of resources to prevent modifying existing rows resource = { ...resource } const hasConflict = resolvedConflicts.some((e) => e.resource.id === resource.id) @@ -176,6 +197,7 @@ export class ResourceTransfer extends ConflictDialog { resource.path = join(this.targetFolder.path, resource.name) resource.webDavPath = join(this.targetFolder.webDavPath, resource.name) movedResources.push(resource) + setProgress({ total: this.resourcesToMove.length, current: i + 1 }) } catch (error) { console.error(error) error.resourceName = resource.name diff --git a/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts b/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts index 7ca9c128464..624d2dbbc0a 100644 --- a/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts +++ b/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts @@ -2,7 +2,7 @@ import { join } from 'path' import { Resource } from 'web-client' import { ResolveConflict, ResolveStrategy } from '.' -interface FileConflict { +export interface FileConflict { resource: Resource strategy?: ResolveStrategy } diff --git a/packages/web-app-files/src/mixins/actions/downloadArchive.ts b/packages/web-app-files/src/mixins/actions/downloadArchive.ts index 93d3d95ce9b..06f12a6358f 100644 --- a/packages/web-app-files/src/mixins/actions/downloadArchive.ts +++ b/packages/web-app-files/src/mixins/actions/downloadArchive.ts @@ -10,6 +10,7 @@ import first from 'lodash-es/first' import { archiverService } from '../../services' import { isPublicSpaceResource, Resource } from 'web-client/src/helpers' import { mapGetters } from 'vuex' +import { loadingService } from 'web-pkg/src/services' export default { mixins: [isFilesAppActive], @@ -69,7 +70,7 @@ export default { } }, methods: { - async $_downloadArchive_trigger({ resources }) { + $_downloadArchive_trigger({ resources }) { const fileOptions = archiverService.fileIdsSupported ? { fileIds: resources.map((resource) => resource.fileId) @@ -78,25 +79,27 @@ export default { dir: path.dirname(first(resources).path) || '/', files: resources.map((resource) => resource.name) } - await archiverService - .triggerDownload({ - ...fileOptions, - ...(isPublicSpaceResource(this.space) && { - publicToken: this.space.id, - publicLinkPassword: this.publicLinkPassword + return loadingService.addTask(() => { + return archiverService + .triggerDownload({ + ...fileOptions, + ...(isPublicSpaceResource(this.space) && { + publicToken: this.space.id, + publicLinkPassword: this.publicLinkPassword + }) }) - }) - .catch((e) => { - console.error(e) - this.showMessage({ - title: this.$ngettext( - 'Failed to download the selected folder.', // on single selection only available for folders - 'Failed to download the selected files.', // on multi selection available for files+folders - this.selectedFiles.length - ), - status: 'danger' + .catch((e) => { + console.error(e) + this.showMessage({ + title: this.$ngettext( + 'Failed to download the selected folder.', // on single selection only available for folders + 'Failed to download the selected files.', // on multi selection available for files+folders + this.selectedFiles.length + ), + status: 'danger' + }) }) - }) + }) } } } diff --git a/packages/web-app-files/src/mixins/actions/emptyTrashBin.ts b/packages/web-app-files/src/mixins/actions/emptyTrashBin.ts index 54c2d65269a..897e71e45c9 100644 --- a/packages/web-app-files/src/mixins/actions/emptyTrashBin.ts +++ b/packages/web-app-files/src/mixins/actions/emptyTrashBin.ts @@ -3,6 +3,7 @@ import { isLocationTrashActive } from '../../router' import { buildWebDavFilesTrashPath } from '../../helpers/resources' import { buildWebDavSpacesTrashPath } from 'web-client/src/helpers' import { isProjectSpaceResource } from 'web-client/src/helpers' +import { loadingService } from 'web-pkg' export default { computed: { @@ -70,27 +71,29 @@ export default { ? buildWebDavSpacesTrashPath(this.space.id) : buildWebDavFilesTrashPath(this.user.id) - return this.$client.fileTrash - .clearTrashBin(path) - .then(() => { - this.showMessage({ - title: this.$gettext('All deleted files were removed') + return loadingService.addTask(() => { + return this.$client.fileTrash + .clearTrashBin(path) + .then(() => { + this.showMessage({ + title: this.$gettext('All deleted files were removed') + }) + this.clearTrashBin() }) - this.clearTrashBin() - }) - .catch((error) => { - console.error(error) - this.showMessage({ - title: this.$pgettext( - 'Error message in case clearing the trash bin fails', - 'Failed to delete all files permanently' - ), - status: 'danger' + .catch((error) => { + console.error(error) + this.showMessage({ + title: this.$pgettext( + 'Error message in case clearing the trash bin fails', + 'Failed to delete all files permanently' + ), + status: 'danger' + }) }) - }) - .finally(() => { - this.hideModal() - }) + .finally(() => { + this.hideModal() + }) + }) } } } diff --git a/packages/web-app-files/src/mixins/actions/restore.ts b/packages/web-app-files/src/mixins/actions/restore.ts index 17337a3029b..180522acf53 100644 --- a/packages/web-app-files/src/mixins/actions/restore.ts +++ b/packages/web-app-files/src/mixins/actions/restore.ts @@ -2,7 +2,7 @@ import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' import { dirname } from 'path' import { isLocationTrashActive } from '../../router' -import { clientService } from 'web-pkg/src/services' +import { clientService, loadingService, LoadingTaskState } from 'web-pkg/src/services' import { Resource, isProjectSpaceResource, extractExtensionFromFile } from 'web-client/src/helpers' import { ResolveStrategy, @@ -165,12 +165,16 @@ export default { existingPaths } }, - async $_restore_restoreResources(resources: Resource[], missingFolderPaths: string[]) { + async $_restore_restoreResources( + resources: Resource[], + missingFolderPaths: string[], + setProgress: (args: LoadingTaskState) => void + ) { const restoredResources = [] const failedResources = [] let createdFolderPaths = [] - for (const resource of resources) { + for (const [i, resource] of resources.entries()) { const parentPath = dirname(resource.path) if (missingFolderPaths.includes(parentPath)) { const { existingPaths } = await this.$_restore_createFolderStructure( @@ -185,6 +189,7 @@ export default { overwrite: true }) restoredResources.push(resource) + setProgress({ total: resources.length, current: i + 1 }) } catch (e) { console.error(e) failedResources.push(resource) @@ -275,7 +280,12 @@ export default { resource.path = urlJoin(parentPath, resolvedName) resolvedResources.push(resource) } - return this.$_restore_restoreResources(resolvedResources, missingFolderPaths) + return loadingService.addTask( + ({ setProgress }) => { + return this.$_restore_restoreResources(resolvedResources, missingFolderPaths, setProgress) + }, + { indeterminate: false } + ) } } } diff --git a/packages/web-app-files/src/mixins/deleteResources.ts b/packages/web-app-files/src/mixins/deleteResources.ts index 66b0f328bfd..d6100c890a9 100644 --- a/packages/web-app-files/src/mixins/deleteResources.ts +++ b/packages/web-app-files/src/mixins/deleteResources.ts @@ -5,7 +5,7 @@ import { buildWebDavFilesTrashPath } from '../helpers/resources' import { buildWebDavSpacesTrashPath } from 'web-client/src/helpers' import PQueue from 'p-queue' import { isLocationTrashActive, isLocationSpacesActive } from '../router' -import { clientService } from 'web-pkg/src/services' +import { clientService, loadingService } from 'web-pkg/src/services' import { dirname } from 'path' import { createFileRouteOptions } from 'web-pkg/src/helpers/router' @@ -109,11 +109,7 @@ export default { return this.$client.fileTrash .clearTrashBin(path, resource.id) .then(() => { - this.removeFilesFromTrashbin([resource]) - const translated = this.$gettext('"%{file}" was deleted successfully') - this.showMessage({ - title: this.$gettextInterpolate(translated, { file: resource.name }, true) - }) + return resource }) .catch((error) => { if (error.statusCode === 423) { @@ -135,68 +131,91 @@ export default { }, $_deleteResources_trashbin_delete() { - // TODO: use clear all if all files are selected - this.toggleModalConfirmButton() - for (const file of this.$_deleteResources_resources) { - const p = this.deleteResources_queue.add(() => { - return this.$_deleteResources_trashbin_deleteOp(file) - }) - this.deleteResources_deleteOps.push(p) - } + return loadingService.addTask( + ({ setProgress }) => { + // TODO: use clear all if all files are selected + this.toggleModalConfirmButton() - Promise.all(this.deleteResources_deleteOps).then(() => { - this.hideModal() - this.toggleModalConfirmButton() - }) - }, - - $_deleteResources_filesList_delete() { - this.deleteFiles({ - ...this.$language, - space: this.space, - files: this.$_deleteResources_resources, - clientService: this.$clientService - }).then(async () => { - this.hideModal() - this.toggleModalConfirmButton() - - // Load quota - if ( - isLocationSpacesActive(this.$router, 'files-spaces-generic') && - !['public', 'share'].includes(this.space?.driveType) - ) { - if (this.capabilities?.spaces?.enabled) { - const accessToken = this.$store.getters['runtime/auth/accessToken'] - const graphClient = clientService.graphAuthenticated( - this.configuration.server, - accessToken - ) - const driveResponse = await graphClient.drives.getDrive( - this.$_deleteResources_resources[0].storageId - ) - this.UPDATE_SPACE_FIELD({ - id: driveResponse.data.id, - field: 'spaceQuota', - value: driveResponse.data.quota + for (const [i, file] of this.$_deleteResources_resources.entries()) { + const p = this.deleteResources_queue.add(() => { + setProgress({ total: this.$_deleteResources_resources.length, current: i + 1 }) + return this.$_deleteResources_trashbin_deleteOp(file) }) - } else { - const user = await this.$client.users.getUser(this.user.id) - this.SET_QUOTA(user.quota) + this.deleteResources_deleteOps.push(p) } - } - if ( - this.resourcesToDelete.length && - isSameResource(this.resourcesToDelete[0], this.currentFolder) - ) { - return this.$router.push( - createFileRouteOptions(this.space, { - path: dirname(this.resourcesToDelete[0].path), - fileId: this.resourcesToDelete[0].parentFolderId - }) - ) - } - }) + return Promise.all(this.deleteResources_deleteOps).then((resources) => { + this.removeFilesFromTrashbin(resources) + const title = + resources.length > 1 + ? this.$gettext('%{count} files were deleted successfully', { + count: resources.length + }) + : this.$gettext('"%{file}" was deleted successfully', { + file: resources[0].name + }) + this.showMessage({ title }) + this.hideModal() + this.toggleModalConfirmButton() + }) + }, + { indeterminate: false } + ) + }, + + $_deleteResources_filesList_delete() { + return loadingService.addTask( + ({ setProgress }) => { + return this.deleteFiles({ + ...this.$language, + space: this.space, + files: this.$_deleteResources_resources, + clientService: this.$clientService, + setProgress + }).then(async () => { + this.hideModal() + this.toggleModalConfirmButton() + + // Load quota + if ( + isLocationSpacesActive(this.$router, 'files-spaces-generic') && + !['public', 'share'].includes(this.space?.driveType) + ) { + if (this.capabilities?.spaces?.enabled) { + const accessToken = this.$store.getters['runtime/auth/accessToken'] + const graphClient = clientService.graphAuthenticated( + this.configuration.server, + accessToken + ) + const driveResponse = await graphClient.drives.getDrive( + this.$_deleteResources_resources[0].storageId + ) + this.UPDATE_SPACE_FIELD({ + id: driveResponse.data.id, + field: 'spaceQuota', + value: driveResponse.data.quota + }) + } else { + const user = await this.$client.users.getUser(this.user.id) + this.SET_QUOTA(user.quota) + } + } + + if ( + this.resourcesToDelete.length && + isSameResource(this.resourcesToDelete[0], this.currentFolder) + ) { + return this.$router.push( + createFileRouteOptions(this.space, { + path: dirname(this.resourcesToDelete[0].path), + fileId: this.resourcesToDelete[0].parentFolderId + }) + ) + } + }) + }, + { indeterminate: false } + ) }, $_deleteResources_delete() { diff --git a/packages/web-app-files/src/store/actions.ts b/packages/web-app-files/src/store/actions.ts index 6a1c08cc081..862f1261fe6 100644 --- a/packages/web-app-files/src/store/actions.ts +++ b/packages/web-app-files/src/store/actions.ts @@ -17,7 +17,7 @@ import { SpaceResource } from 'web-client/src/helpers' import { WebDAV } from 'web-client/src/webdav' -import { ClientService } from 'web-pkg/src/services' +import { ClientService, loadingService, LoadingTaskState } from 'web-pkg/src/services' import { Language } from 'vue3-gettext' import { DavProperty } from 'web-client/src/webdav/constants' import { AncestorMetaData } from 'web-app-files/src/helpers/resource/ancestorMetaData' @@ -106,20 +106,30 @@ export default { if (context.state.clipboardAction === ClipboardActions.Copy) { movedResources = await copyMove.perform(TransferType.COPY) } - context.commit('CLEAR_CLIPBOARD') - const loadingResources = [] - for (const resource of movedResources) { - loadingResources.push( - (async () => { - const movedResource = await (clientService.webdav as WebDAV).getFileInfo( - targetSpace, - resource + + await loadingService.addTask( + () => { + context.commit('CLEAR_CLIPBOARD') + const loadingResources = [] + const fetchedResources = [] + for (const resource of movedResources) { + loadingResources.push( + (async () => { + const movedResource = await (clientService.webdav as WebDAV).getFileInfo( + targetSpace, + resource + ) + fetchedResources.push(movedResource) + })() ) - context.commit('UPSERT_RESOURCE', movedResource) - })() - ) - } - await Promise.all(loadingResources) + } + + return Promise.all(loadingResources).then(() => { + context.commit('UPSERT_RESOURCES', fetchedResources) + }) + }, + { debounceTime: 0 } + ) }, resetFileSelection(context) { context.commit('RESET_SELECTION') @@ -148,6 +158,7 @@ export default { space: SpaceResource files: Resource[] clientService: ClientService + setProgress: (args: LoadingTaskState) => void firstRun: boolean } & Language ) { @@ -157,6 +168,7 @@ export default { space, files, clientService, + setProgress, firstRun = true } = options const promises = [] @@ -166,6 +178,7 @@ export default { .deleteFile(space, file) .then(() => { removedFiles.push(file) + setProgress({ total: files.length, current: removedFiles.length }) }) .catch((error) => { let translated = $gettext('Failed to delete "%{file}"') diff --git a/packages/web-app-files/src/store/mutations.ts b/packages/web-app-files/src/store/mutations.ts index 337b3aec37a..ed24b5d07fd 100644 --- a/packages/web-app-files/src/store/mutations.ts +++ b/packages/web-app-files/src/store/mutations.ts @@ -156,6 +156,11 @@ export default { $_upsertResource(state, resource, true) }, + UPSERT_RESOURCES(state, resources) { + const otherFiles = state.files.filter((f) => !resources.some((r) => r.id === f.id)) + state.files = [...otherFiles, ...resources] + }, + /** * Updates the given resource in the store. If the resource doesn't exist in the store, the update * will be ignored. If you also want to allow inserts, use UPSERT_RESOURCE instead. diff --git a/packages/web-app-files/src/views/spaces/GenericSpace.vue b/packages/web-app-files/src/views/spaces/GenericSpace.vue index 10d0dce585f..00885b1663e 100644 --- a/packages/web-app-files/src/views/spaces/GenericSpace.vue +++ b/packages/web-app-files/src/views/spaces/GenericSpace.vue @@ -424,11 +424,7 @@ export default defineComponent({ methods: { ...mapActions('Files', ['loadPreview']), ...mapActions(['showMessage', 'createModal', 'hideModal']), - ...mapMutations('Files', [ - 'REMOVE_FILES', - 'REMOVE_FILES_FROM_SEARCHED', - 'REMOVE_FILE_SELECTION' - ]), + ...mapMutations('Files', ['REMOVE_FILES', 'REMOVE_FILES_FROM_SEARCHED', 'RESET_SELECTION']), async fileDropped(fileIdTarget) { const selected = [...this.selectedResources] @@ -440,6 +436,7 @@ export default defineComponent({ if (targetFolder.type !== 'folder') { return } + const copyMove = new ResourceTransfer( this.space, selected, @@ -454,11 +451,9 @@ export default defineComponent({ this.$gettextInterpolate ) const movedResources = await copyMove.perform(TransferType.MOVE) - for (const resource of movedResources) { - this.REMOVE_FILES([resource]) - this.REMOVE_FILES_FROM_SEARCHED([resource]) - this.REMOVE_FILE_SELECTION(resource) - } + this.REMOVE_FILES(movedResources) + this.REMOVE_FILES_FROM_SEARCHED(movedResources) + this.RESET_SELECTION() }, rowMounted(resource, component, dimensions) { diff --git a/packages/web-pkg/src/components/LoadingIndicator.vue b/packages/web-pkg/src/components/LoadingIndicator.vue new file mode 100644 index 00000000000..f290a8e1d6f --- /dev/null +++ b/packages/web-pkg/src/components/LoadingIndicator.vue @@ -0,0 +1,58 @@ + + + + diff --git a/packages/web-pkg/src/services/index.ts b/packages/web-pkg/src/services/index.ts index 8df93027a5e..5ac04bafe6b 100644 --- a/packages/web-pkg/src/services/index.ts +++ b/packages/web-pkg/src/services/index.ts @@ -1,3 +1,4 @@ export * from './cache' export * from './client' export * from './eventBus' +export * from './loadingService' diff --git a/packages/web-pkg/src/services/loadingService.ts b/packages/web-pkg/src/services/loadingService.ts new file mode 100644 index 00000000000..ae328d52b0a --- /dev/null +++ b/packages/web-pkg/src/services/loadingService.ts @@ -0,0 +1,100 @@ +import * as uuid from 'uuid' +import { eventBus } from 'web-pkg' +import { debounce } from 'lodash-es' + +export enum LoadingEventTopics { + add = 'loading-service.add', + remove = 'loading-service.remove', + setProgress = 'loading-service.set-progress' +} + +export interface LoadingTaskState { + total: number + current: number +} + +export interface LoadingTask { + id: string + active: boolean + state?: LoadingTaskState +} + +// time until a loading task is being set active +const DEFAULT_DEBOUNCE_TIME = 200 + +export class LoadingService { + private tasks: LoadingTask[] = [] + + public get isLoading(): boolean { + return this.tasks.some((e) => e.active) + } + + /** + * Get the current progress from 0 to 100. + * Returns null if at least one task is indeterminate. + */ + public get currentProgress(): number | null { + if (this.tasks.some((e) => !e.state && e.active)) { + return null + } + + const tasks = this.tasks.filter((e) => !!e.state && e.active) + if (!tasks.length) { + return null + } + + const progress = tasks.reduce((acc, task) => { + acc += task.state.current / task.state.total + return acc + }, 0) + + return Math.round((progress / tasks.length) * 100) + } + + public addTask( + callback: ({ setProgress }: { setProgress: (args: LoadingTaskState) => void }) => Promise, + { + debounceTime = DEFAULT_DEBOUNCE_TIME, + indeterminate = true + }: { debounceTime?: number; indeterminate?: boolean } = {} + ): Promise { + const task = { + id: uuid.v4(), + active: false, + ...(!indeterminate && { state: { total: 0, current: 0 } }) + } + this.tasks.push(task) + + const debounced = debounce(() => { + task.active = true + eventBus.publish(LoadingEventTopics.add) + }, debounceTime) + debounced() + + const setProgress = ({ total, current }: LoadingTaskState) => { + if (!indeterminate) { + this.setProgress({ task, total, current }) + } + } + + return callback({ setProgress }).finally(() => { + this.removeTask(task.id) + }) + } + + private removeTask(id: string): void { + this.tasks = this.tasks.filter((e) => e.id !== id) + eventBus.publish(LoadingEventTopics.remove) + } + + private setProgress({ task, total, current }): void { + if (!task.state) { + task.state = { total: 0, current: 0 } + } + task.state.total = total + task.state.current = current + eventBus.publish(LoadingEventTopics.setProgress) + } +} + +export const loadingService = new LoadingService() diff --git a/packages/web-runtime/src/layouts/Application.vue b/packages/web-runtime/src/layouts/Application.vue index 91305eb10b2..7caba08137d 100644 --- a/packages/web-runtime/src/layouts/Application.vue +++ b/packages/web-runtime/src/layouts/Application.vue @@ -1,5 +1,6 @@